/**************************************************************************************
 * Copyright (c) Jonas BonŽr, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the LGPL license      *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package org.codehaus.aspectwerkz.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Iterator;

import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
import org.codehaus.aspectwerkz.transform.TransformationConstants;

/**
 * Helper class with utility methods for working with the java.lang.reflect.* package.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 */
public class ReflectHelper {

    private final static Method OBJECT_EQUALS;
    private final static Method OBJECT_HASH_CODE;
    private final static Method OBJECT_GET_CLASS;
    private final static Method OBJECT_TO_STRING;
    private final static Method OBJECT_CLONE;
    private final static Method OBJECT_WAIT_1;
    private final static Method OBJECT_WAIT_2;
    private final static Method OBJECT_WAIT_3;
    private final static Method OBJECT_NOTIFY;
    private final static Method OBJECT_NOTIFY_ALL;
    private final static Method OBJECT_FINALIZE;

    static {
        Class clazz = Object.class;
        try {
            OBJECT_EQUALS = clazz.getDeclaredMethod("equals", new Class[]{clazz});
            OBJECT_HASH_CODE = clazz.getDeclaredMethod("hashCode", new Class[]{});
            OBJECT_GET_CLASS = clazz.getDeclaredMethod("getClass", new Class[]{});
            OBJECT_CLONE = clazz.getDeclaredMethod("clone", new Class[]{});
            OBJECT_TO_STRING = clazz.getDeclaredMethod("toString", new Class[]{});
            OBJECT_WAIT_1 = clazz.getDeclaredMethod("wait", new Class[]{});
            OBJECT_WAIT_2 = clazz.getDeclaredMethod("wait", new Class[]{long.class});
            OBJECT_WAIT_3 = clazz.getDeclaredMethod("wait", new Class[]{long.class, int.class});
            OBJECT_NOTIFY = clazz.getDeclaredMethod("notify", new Class[]{});
            OBJECT_NOTIFY_ALL = clazz.getDeclaredMethod("notifyAll", new Class[]{});
            OBJECT_FINALIZE = clazz.getDeclaredMethod("finalize", new Class[]{});
        } catch (NoSuchMethodException e) {
            throw new WrappedRuntimeException(e);
        }
    }

//    /**
//     * Creates a sorted method list of all the methods in the class and super classes, including package private ones.
//     *
//     * @param klass the class with the methods
//     * @return the sorted method list
//     */
//    public static List createSortedMethodList(final Class klass) {
//        if (klass == null) {
//            throw new IllegalArgumentException("class to sort method on can not be null");
//        }
//
//        // get all public methods including the inherited methods
//        java.lang.reflect.Method[] methods = klass.getMethods();
//        java.lang.reflect.Method[] privateMethods = klass.getDeclaredMethods();
//        List methodList = new ArrayList(methods.length);
//        for (int i = 0; i < methods.length; i++) {
//            Method method = methods[i];
//            if (ReflectHelper.isUserDefinedMethod(method)) {
//                methodList.add(method);
//            }
//        }
//        // lookup in declared method to add "package private" method (which can be Pointcut with signatures)
//        for (int i = 0; i < privateMethods.length; i++) {
//            Method method = privateMethods[i];
//            if (ReflectHelper.isUserDefinedMethod(method) && !methodList.contains(method)) {
//                methodList.add(method);
//            }
//        }
//
//        Collections.sort(methodList, MethodComparator.getInstance(MethodComparator.NORMAL_METHOD));
//        return methodList;
//    }

//    /**
//     * Creates a sorted method list of all the methods in the class and super classes, if and only
//     * if those are part of the given list of interfaces declared method
//     *
//     * @param klass                    the class with the methods
//     * @param interfaceDeclaredMethods the list of interface declared methods
//     * @return the sorted method list
//     */
//    public static List createInterfaceDefinedSortedMethodList(final Class klass, List interfaceDeclaredMethods) {
//        if (klass == null) {
//            throw new IllegalArgumentException("class to sort method on can not be null");
//        }
//
//        // get all public methods including the inherited methods
//        java.lang.reflect.Method[] methods = klass.getMethods();
//        java.lang.reflect.Method[] privateMethods = klass.getDeclaredMethods();
//        List methodList = new ArrayList(methods.length);
//        for (int i = 0; i < methods.length; i++) {
//            Method method = methods[i];
//            if (ReflectHelper.isUserDefinedMethod(method) && isDeclaredByInterface(method, interfaceDeclaredMethods)) {
//                methodList.add(method);
//            }
//        }
//        // lookup in declared method to add "package private" method (which can be Pointcut with signatures)
//        for (int i = 0; i < privateMethods.length; i++) {
//            Method method = privateMethods[i];
//            if (ReflectHelper.isUserDefinedMethod(method) && isDeclaredByInterface(method, interfaceDeclaredMethods)
//                && !methodList.contains(method)) {
//                methodList.add(method);
//            }
//        }
//
//        Collections.sort(methodList, MethodComparator.getInstance(MethodComparator.NORMAL_METHOD));
//        return methodList;
//    }

    /**
     * Returns true if the method is declared by one of the given method declared in an interface class
     *
     * @param method
     * @param interfaceDeclaredMethods
     * @return
     */
    private static boolean isDeclaredByInterface(Method method, List interfaceDeclaredMethods) {
        boolean match = false;
        for (Iterator iterator = interfaceDeclaredMethods.iterator(); iterator.hasNext();) {
            Method methodIt = (Method) iterator.next();
            if (method.getName().equals(methodIt.getName())) {
                if (method.getParameterTypes().length == methodIt.getParameterTypes().length) {
                    boolean matchArgs = true;
                    for (int i = 0; i < method.getParameterTypes().length; i++) {
                        // BAD ! will lead to nested loading while system not ready
                        // => if introduced method has target class in its signature weaving will not occur
                        // properly
                        // ?? should we use ASMInfo ?
                        Class parameterType = method.getParameterTypes()[i];
                        if (parameterType.getName().equals(methodIt.getParameterTypes()[i].getName())) {
                            ;
                        } else {
                            matchArgs = false;
                            break;
                        }
                    }
                    if (matchArgs) {
                        match = true;
                        break;
                    }
                }
            }
        }
        return match;
    }

    /**
     * Returns true if the method is not of on java.lang.Object and is not an AW generated one
     *
     * @param method
     * @return
     */
    private static boolean isUserDefinedMethod(final Method method) {
        if (!method.equals(OBJECT_EQUALS)
            && !method.equals(OBJECT_HASH_CODE)
            && !method.equals(OBJECT_GET_CLASS)
            && !method.equals(OBJECT_TO_STRING)
            && !method.equals(OBJECT_CLONE)
            && !method.equals(OBJECT_WAIT_1)
            && !method.equals(OBJECT_WAIT_2)
            && !method.equals(OBJECT_WAIT_3)
            && !method.equals(OBJECT_NOTIFY)
            && !method.equals(OBJECT_NOTIFY_ALL)
            && !method.getName().startsWith(TransformationConstants.SYNTHETIC_MEMBER_PREFIX)
            && !method.getName().startsWith(TransformationConstants.ORIGINAL_METHOD_PREFIX)
            && !method.getName().startsWith(TransformationConstants.ASPECTWERKZ_PREFIX)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Converts modifiers represented in a string array to an int.
     *
     * @param modifiers the modifiers as strings
     * @return the modifiers as an int
     */
    public static int getModifiersAsInt(final String[] modifiers) {
        int accessFlags = 0;
        for (int i = 0; i < modifiers.length; i++) {
            if (modifiers[i].equals("abstract")) {
                accessFlags |= Modifier.ABSTRACT;
            } else if (modifiers[i].equals("final")) {
                accessFlags |= Modifier.FINAL;
            } else if (modifiers[i].equals("interface")) {
                accessFlags |= Modifier.INTERFACE;
            } else if (modifiers[i].equals("native")) {
                accessFlags |= Modifier.NATIVE;
            } else if (modifiers[i].equals("private")) {
                accessFlags |= Modifier.PRIVATE;
            } else if (modifiers[i].equals("protected")) {
                accessFlags |= Modifier.PROTECTED;
            } else if (modifiers[i].equals("public")) {
                accessFlags |= Modifier.PUBLIC;
            } else if (modifiers[i].equals("static")) {
                accessFlags |= Modifier.STATIC;
            } else if (modifiers[i].equals("strict")) {
                accessFlags |= Modifier.STRICT;
            } else if (modifiers[i].equals("synchronized")) {
                accessFlags |= Modifier.SYNCHRONIZED;
            } else if (modifiers[i].equals("transient")) {
                accessFlags |= Modifier.TRANSIENT;
            } else if (modifiers[i].equals("volatile")) {
                accessFlags |= Modifier.VOLATILE;
            }
        }
        return accessFlags;
    }

    /**
     * Calculate the hash for a class.
     *
     * @param klass the class
     * @return the hash
     */
    public static int calculateHash(final Class klass) {
        return klass.getName().hashCode();
    }

    /**
     * Calculate the hash for a method.
     *
     * @param method the method
     * @return the hash
     */
    public static int calculateHash(final java.lang.reflect.Method method) {
        int hash = 17;
        hash = (37 * hash) + method.getName().hashCode();
        for (int i = 0; i < method.getParameterTypes().length; i++) {
            Class type = method.getParameterTypes()[i];
            hash = (37 * hash) + type.getName().hashCode();
        }
        return hash;
    }

    /**
     * Calculate the hash for a constructor.
     *
     * @param constructor the constructor
     * @return the hash
     */
    public static int calculateHash(final Constructor constructor) {
        int hash = 17;
        hash = (37 * hash) + TransformationConstants.INIT_METHOD_NAME.hashCode();
        for (int i = 0; i < constructor.getParameterTypes().length; i++) {
            Class type = constructor.getParameterTypes()[i];
            hash = (37 * hash) + type.getName().replace('/', '.').hashCode();
        }
        return hash;
    }

    /**
     * Calculate the hash for a field.
     *
     * @param field the field
     * @return the hash
     */
    public static int calculateHash(final Field field) {
        int hash = 17;
        hash = (37 * hash) + field.getName().hashCode();
        Class type = field.getType();
        hash = (37 * hash) + type.getName().hashCode();
        return hash;
    }

    /**
     * Checks if the class is a of a primitive type, if so create and return the class for the type else return null.
     *
     * @param className
     * @return the class for the primitive type or null
     */
    public static Class getPrimitiveClass(final String className) {
        if (className.equals("void")) {
            return void.class;
        } else if (className.equals("long")) {
            return long.class;
        } else if (className.equals("int")) {
            return int.class;
        } else if (className.equals("short")) {
            return short.class;
        } else if (className.equals("double")) {
            return double.class;
        } else if (className.equals("float")) {
            return float.class;
        } else if (className.equals("byte")) {
            return byte.class;
        } else if (className.equals("boolean")) {
            return boolean.class;
        } else if (className.equals("char")) {
            return char.class;
        } else {
            return null;
        }
    }

    /**
     * Returns JVM type signature for given class.
     *
     * @param cl
     * @return
     */
    public static String getClassSignature(Class cl) {
        StringBuffer sbuf = new StringBuffer();
        while (cl.isArray()) {
            sbuf.append('[');
            cl = cl.getComponentType();
        }
        if (cl.isPrimitive()) {
            if (cl == Integer.TYPE) {
                sbuf.append('I');
            } else if (cl == Byte.TYPE) {
                sbuf.append('B');
            } else if (cl == Long.TYPE) {
                sbuf.append('J');
            } else if (cl == Float.TYPE) {
                sbuf.append('F');
            } else if (cl == Double.TYPE) {
                sbuf.append('D');
            } else if (cl == Short.TYPE) {
                sbuf.append('S');
            } else if (cl == Character.TYPE) {
                sbuf.append('C');
            } else if (cl == Boolean.TYPE) {
                sbuf.append('Z');
            } else if (cl == Void.TYPE) {
                sbuf.append('V');
            } else {
                throw new InternalError();
            }
        } else {
            sbuf.append('L' + cl.getName().replace('.', '/') + ';');
        }
        return sbuf.toString();
    }

    /**
     * Returns JVM type signature for a constructor.
     *
     * @param constructor
     * @return
     */
    public static String getConstructorSignature(final Constructor constructor) {
        return getMethodSignature(constructor.getParameterTypes(), Void.TYPE);
    }

    /**
     * Returns JVM type signature for a field.
     *
     * @param field
     * @return
     */
    public static String getFieldSignature(final Field field) {
        return getClassSignature(field.getType());
    }

    /**
     * Returns JVM type signature for a method.
     *
     * @param method
     * @return
     */
    public static String getMethodSignature(final Method method) {
        return getMethodSignature(method.getParameterTypes(), method.getReturnType());
    }

    /**
     * Returns JVM type signature for given list of parameters and return type.
     *
     * @param paramTypes
     * @param retType
     * @return
     */
    private static String getMethodSignature(Class[] paramTypes, Class retType) {
        StringBuffer sbuf = new StringBuffer();
        sbuf.append('(');
        for (int i = 0; i < paramTypes.length; i++) {
            sbuf.append(getClassSignature(paramTypes[i]));
        }
        sbuf.append(')');
        sbuf.append(getClassSignature(retType));
        return sbuf.toString();
    }
}