/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import net.ranides.assira.text.StringLoader;

/**
 *
 * @author ranides
 */
public final class ReflectUtils {
    
    private ReflectUtils() { /* utility class */ }
    
    /**
     * Próbuje wymusić prawa dostępu do podanego obiektu uzyskanego za pomocą refleksji
     * (np zyskać uprawnienia do wywołania prywatnej metody, albo odczytu prywatnego pola).
     * @param <T>
     * @param object
     * @return
     */
    public static <T extends AccessibleObject> T access(T object) {
        if( !object.isAccessible() ) { object.setAccessible(true); }
        return object;
    }
    
    /**
     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa. 
     * Oczywiście, to nie działa idealnie dla klas, które nie mają pliku źródłowego
     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana 
     * jest cała zawartość, w którym je zapisano, a nie tylko fragment. 
     * <p>
     * Kod źródłowy musi być dostępny dla ClassLoadera powiązanego z bieżącym wątkiem.
     * </p>
     * 
     * @param classname
     * @return 
     */
    public static String getSource(String classname) {
        return readSourceCode(Thread.currentThread().getContextClassLoader(), classname);
    }
    
    /**
     * Zwraca zawartość pliku źródłowego, w którym zapisana jest podana klasa. 
     * Oczywiście, to nie działa idealnie dla klas, które nie mają pliku źródłowego
     * w sensie stricte. Dla klas zagnieżdżonych albo anonimowych zwracana 
     * jest cała zawartość, w którym je zapisano, a nie tylko fragment. 
     * <p>
     * Kod źródłowy musi być dostępny dla classloadera, który załadował klasę.
     * </p>
     * @param clazz
     * @return 
     */
    public static String getSource(Class<?> clazz) {
        return readSourceCode(clazz.getClassLoader(), clazz.getName());
    }
    
    private static String readSourceCode(ClassLoader loader, String classname) {
        String file = classname.replace('.', '/').replaceAll("\\$.+", "") + ".java";
        InputStream istream = loader.getResourceAsStream(file);
        if(istream != null) {
            return StringLoader.fromStream(istream);
        }
        if(new File(file).exists() ) {
            return StringLoader.fromFile(file);
        }
        if(new File("src/test/java/" + file).exists() ) {
            return StringLoader.fromFile("src/test/java/" + file);
        }
        if(new File("src/test/main/" + file).exists() ) {
            return StringLoader.fromFile("src/test/main/" + file);
        }
        throw new AssertionError(new IOException("unknown location of file: " + file));
    }
    
    /**
     * Zwraca lokalizację, w której został wyszukany odpowiedni plik .class z
     * definicją załadowanej klasy. Lokalizacja nie wskazuje na  konkretny
     * plik .class, aby uzyskać pełną ścieżkę, należy uzyć metody {@link #getFQLocation}
     * @param clazz
     * @return
     */
    public static String getLocation(Class<?> clazz) {
        return clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
    }
    
    /**
     * Zwraca ścieżkę do pliku .class, z którego została załadowana klasa. Aby
     * uzyskać lokalizację, w której rozpoczęto wyszukiwanie pliku .class, należy
     * użyć metody {@link #getLocation}
     * @param clazz
     * @return
     */
    public static String getFQLocation(Class<?> clazz) {
        return clazz.getClassLoader().getResource( clazz.getName().replace('.', '/') + ".class" ).getFile();
    }
}
