/*
 * @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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import net.ranides.assira.asm.MethodInspectorVM;
import net.ranides.assira.reflection.GenericClass;
import net.ranides.assira.reflection.InspectException;
import net.ranides.assira.reflection.InvokeException;
import net.ranides.assira.reflection.GenericParam;
import static net.ranides.assira.reflection.util.ReflectUtils.access;

/**
 *
 * @author Ranides Atterwim <ranides@gmail.com>
 */
public final class MethodUtils {
    
    private MethodUtils() {
        // utility class
    }

    /**
     * Ustala typ zwracany przez przekazaną metodę. W przeciwieństwie do
     * Reflection API, obsługuje również typy generyczne, wykrywając generic parameters.
     * @param method
     * @return
     */
    public static GenericClass typeof(Method method) {
        return MethodInspectorVM.typeof(method);
    }

    /**
     * Zwraca listę argumentów pobieranych przez metodę. Reflection API pozwala
     * jedynie ustalić typy argumentów, bez nazw. Nie wspiera również typów generycznych.
     * Poniższa metoda umożliwia ustalenie zarówno nazw, jak i parametrów typów
     * generycznych.
     * @param method
     * @return
     */
    public static List<GenericParam> params(Method method) {
        return MethodInspectorVM.params(method);
    }
    
    /**
     * Sprawdza, czy podana metoda jest nadpisana przez metodę (override). Cały dowcip
     * to obsługa metod package-scope, które może przeładować klasa z tego samego
     * pakietu, a z innego nie.
     * @param candidate 
     * @param original 
     * @return 
     */
    public static boolean overrides(Method candidate, Method original) {
        if( !original.getName().equals(candidate.getName()) ) {
            return false;
        }
        if( MemberUtils.isPrivate(original) ) {
            return false;
        }
        // package scope check
        if( !MemberUtils.isPublic(original) && !MemberUtils.isProtected(original)) {
            Package opackage = original.getDeclaringClass().getPackage();
            Package cpackage = candidate.getDeclaringClass().getPackage();
            if( !opackage.equals(cpackage) ) {
                return false;
            }
        }
        return Arrays.equals(original.getParameterTypes(), candidate.getParameterTypes());
    }
    
    /**
     * Wywołuje na rzecz obiektu podaną metodę. 
     * @param object
     * @param method
     * @param arguments
     * @return
     * @throws InspectException
     */
    public static Object invoke(Object object, Method method, Object... arguments) throws InspectException, InvokeException {
        assert object != null || MemberUtils.isStatic(method);
        try {
            return access(method).invoke(object, arguments);
        } catch (IllegalAccessException | IllegalArgumentException ex) {
            throw new InspectException(ex);
        } catch (InvocationTargetException ex) {
            throw new InvokeException(ex);
        }
    }
    
}
