/*
 * @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 java.nio.file.Path;
import java.security.CodeSource;
import net.ranides.assira.io.PathUtils;
import net.ranides.assira.system.RuntimeUtils;
import net.ranides.assira.text.Charsets;
import net.ranides.assira.text.IOStrings;

/**
 *
 * @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() ) { 
            return object;
        }
        try {
            RuntimeUtils.doPrivileged(() -> object.setAccessible(true));
        } catch(SecurityException $0) { // NOPMD
            // ignore
        }
        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) throws IOException {
        return readSourceCode(ClassUtils.loader(), 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) throws IOException {
        return readSourceCode(clazz.getClassLoader(), clazz.getName());
    }
    
    private static String readSourceCode(ClassLoader loader, String classname) throws IOException {
        String name = classname.replace('.', '/').replaceAll("\\$.+", "") + ".java";
        InputStream istream = loader.getResourceAsStream(name);
        if(istream != null) {
            return IOStrings.read(istream, Charsets.UTF8);
        }

        File file;        
        if((file = new File(name)).exists()) {
            return IOStrings.read(file, Charsets.UTF8);
        }
        if((file = new File("src/test/java/" + name)).exists()) {
            return IOStrings.read(file, Charsets.UTF8);
        }
        if((file = new File("src/main/java/" + name)).exists()) {
            return IOStrings.read(file, Charsets.UTF8);
        }
        throw new IOException("unknown location of file: " + name);
    }
    
    /**
     * 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 Path getLocation(Class<?> clazz) {
        CodeSource cs = clazz.getProtectionDomain().getCodeSource();
        if(null == cs) {
            throw new RuntimeException("Unknown class location: "+ clazz);
        }
        return PathUtils.fromURL(cs.getLocation());
    }
    
    /**
     * 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 Path getFQLocation(Class<?> clazz) {
        ClassLoader cr = clazz.getClassLoader();
        if(null == cr) {
            throw new RuntimeException("Unknown class location: "+ clazz);
        }
        String name = clazz.getName().replace('.', '/').replace('\\','/') + ".class";
        return PathUtils.fromURL(cr.getResource(name));
    }

}
