/**************************************************************************************
 * 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.annotation;

import org.codehaus.aspectwerkz.reflect.MethodInfo;
import org.codehaus.aspectwerkz.reflect.ClassInfo;
import org.codehaus.aspectwerkz.reflect.ConstructorInfo;
import org.codehaus.aspectwerkz.reflect.FieldInfo;
import org.codehaus.aspectwerkz.reflect.ReflectHelper;
import org.codehaus.aspectwerkz.reflect.impl.asm.AsmClassInfo;
import org.codehaus.aspectwerkz.annotation.instrumentation.asm.AsmAnnotations;

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

/**
 * Utility class for annotation retrieval.
 * <br/>Note: Annotations are extracted out of ASMClassInfo
 * <br/>Note: caution when changing that to use reflection, since it might lead to load target class during aspect
 * system startup.
 *
 * @author <a href="mailto:jboner@codehaus.org">Jonas BonŽr </a>
 */
public final class Annotations {

    private final static List EMPTY_LIST = new ArrayList();

    /**
     * Return the annotation with a specific name for a specific class.
     *
     * @param annotationName the annotation name
     * @param klass          the java.lang.Class object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final String annotationName, final Class klass) {
        ClassInfo classInfo = AsmClassInfo.getClassInfo(klass.getName(), klass.getClassLoader());
        return AsmAnnotations.getAnnotation(annotationName, classInfo);
    }

    /**
     * Return the annotation with a specific name for a specific class.
     *
     * @param annotation the annotation class
     * @param klass      the java.lang.Class object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final Class annotation, final Class klass) {
        return getAnnotation(getAnnnotationName(annotation), klass);
    }

    /**
     * Return the annotation with a specific name for a specific method.
     *
     * @param annotationName the annotation name
     * @param method         the java.lang.refect.Method object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final String annotationName, final Method method) {
        ClassLoader loader = method.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(method.getDeclaringClass().getName(), loader);
        MethodInfo methodInfo = classInfo.getMethod(ReflectHelper.calculateHash(method));
        return AsmAnnotations.getAnnotation(annotationName, methodInfo);
    }

    /**
     * Return the annotation with a specific name for a specific method.
     *
     * @param annotation the annotation class
     * @param method     the java.lang.refect.Method object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final Class annotation, final Method method) {
        return getAnnotation(getAnnnotationName(annotation), method);
    }

    /**
     * Return the annotation with a specific name for a specific constructor.
     *
     * @param annotationName the annotation name
     * @param constructor    the java.lang.refect.Constructor object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final String annotationName, final Constructor constructor) {
        ClassLoader loader = constructor.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(constructor.getDeclaringClass().getName(), loader);
        ConstructorInfo constructorInfo = classInfo.getConstructor(ReflectHelper.calculateHash(constructor));
        return AsmAnnotations.getAnnotation(annotationName, constructorInfo);
    }

    /**
     * Return the annotation with a specific name for a specific constructor.
     *
     * @param annotation  the annotation class
     * @param constructor the java.lang.refect.Constructor object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final Class annotation, final Constructor constructor) {
        return getAnnotation(getAnnnotationName(annotation), constructor);
    }

    /**
     * Return the annotation with a specific name for a specific field.
     *
     * @param annotationName the annotation name
     * @param field          the java.lang.reflect.Field object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final String annotationName, final Field field) {
        ClassLoader loader = field.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(field.getDeclaringClass().getName(), loader);
        FieldInfo fieldInfo = classInfo.getField(ReflectHelper.calculateHash(field));
        return AsmAnnotations.getAnnotation(annotationName, fieldInfo);
    }

    /**
     * Return the annotation with a specific name for a specific field.
     *
     * @param annotation the annotation class
     * @param field      the java.lang.reflect.Field object to find the annotation on.
     * @return the annotation or null
     */
    public static Annotation getAnnotation(final Class annotation, final Field field) {
        return getAnnotation(getAnnnotationName(annotation), field);
    }

    /**
     * Return a list with the annotations with a specific name for a specific class.
     *
     * @param annotationName the annotation name
     * @param klass          the java.lang.Class object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final String annotationName, final Class klass) {
        ClassInfo classInfo = AsmClassInfo.getClassInfo(klass.getName(), klass.getClassLoader());
        return AsmAnnotations.getAnnotations(annotationName, classInfo);
    }

    /**
     * Return a list with the annotations with a specific name for a specific class.
     *
     * @param annotation the annotation class
     * @param klass      the java.lang.Class object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final Class annotation, final Class klass) {
        return getAnnotations(getAnnnotationName(annotation), klass);
    }

    /**
     * Return a list with the annotations with a specific name for a specific method.
     *
     * @param annotationName the annotation name
     * @param method         the java.lang.refect.Method object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final String annotationName, final Method method) {
        ClassLoader loader = method.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(method.getDeclaringClass().getName(), loader);
        MethodInfo methodInfo = classInfo.getMethod(ReflectHelper.calculateHash(method));
        return AsmAnnotations.getAnnotations(annotationName, methodInfo);
    }

    /**
     * Return a list with the annotations with a specific name for a specific method.
     *
     * @param annotation the annotation class
     * @param method     the java.lang.refect.Method object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final Class annotation, final Method method) {
        return getAnnotations(getAnnnotationName(annotation), method);
    }

    /**
     * Return a list with the annotations with a specific name for a specific constructor.
     *
     * @param annotationName the annotation name
     * @param constructor    the java.lang.refect.Constructor object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final String annotationName, final Constructor constructor) {
        ClassLoader loader = constructor.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(constructor.getDeclaringClass().getName(), loader);
        ConstructorInfo constructorInfo = classInfo.getConstructor(ReflectHelper.calculateHash(constructor));
        return AsmAnnotations.getAnnotations(annotationName, constructorInfo);
    }

    /**
     * Return a list with the annotations with a specific name for a specific constructor.
     *
     * @param annotation  the annotation class
     * @param constructor the java.lang.refect.Constructor object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final Class annotation, final Constructor constructor) {
        return getAnnotations(getAnnnotationName(annotation), constructor);
    }

    /**
     * Return a list with the annotations with a specific name for a specific field.
     *
     * @param annotationName the annotation name
     * @param field          the java.lang.reflect.Field object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final String annotationName, final Field field) {
        ClassLoader loader = field.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(field.getDeclaringClass().getName(), loader);
        FieldInfo fieldInfo = classInfo.getField(ReflectHelper.calculateHash(field));
        return AsmAnnotations.getAnnotations(annotationName, fieldInfo);
    }

    /**
     * Return a list with the annotations with a specific name for a specific field.
     *
     * @param annotation the annotation class
     * @param field      the java.lang.reflect.Field object to find the annotation on.
     * @return the annotations in a list (can be empty)
     */
    public static List getAnnotations(final Class annotation, final Field field) {
        return getAnnotations(getAnnnotationName(annotation), field);
    }

    /**
     * Return a list with the annotations for a specific class. <p/>Each annotation is wrapped in {@link
     * org.codehaus.aspectwerkz.annotation.AnnotationInfo}instance.
     *
     * @param klass the java.lang.Class object to find the annotation on.
     * @return a list with the annotations
     */
    public static List getAnnotationInfos(final Class klass) {
        return AsmClassInfo.getClassInfo(klass.getName(), klass.getClassLoader()).getAnnotations();
    }

    /**
     * Return the annotations for a specific method. <p/>Each annotation is wrapped in {@link
     * org.codehaus.aspectwerkz.annotation.AnnotationInfo}instance.
     *
     * @param method the java.lang.refect.Method object to find the annotation on.
     * @return a list with the annotations
     */
    public static List getAnnotationInfos(final Method method) {
        ClassLoader loader = method.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(method.getDeclaringClass().getName(), loader);
        // AW methods like aw$initJoinPoints may not be visible
        MethodInfo methodInfo = classInfo.getMethod(ReflectHelper.calculateHash(method));
        if (methodInfo != null) {
            return methodInfo.getAnnotations();
        } else {
            return EMPTY_LIST;
        }
    }

    /**
     * Return the annotations for a specific constructor. <p/>Each annotation is wrapped in {@link
     * org.codehaus.aspectwerkz.annotation.AnnotationInfo}instance.
     *
     * @param constructor the java.lang.reflect.Constructor object to find the annotation on.
     * @return a list with the annotations
     */
    public static List getAnnotationInfos(final Constructor constructor) {
        ClassLoader loader = constructor.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(constructor.getDeclaringClass().getName(), loader);
        return classInfo.getConstructor(ReflectHelper.calculateHash(constructor)).getAnnotations();
    }

    /**
     * Return the annotations for a specific field. <p/>Each annotation is wrapped in {@link
     * org.codehaus.aspectwerkz.annotation.AnnotationInfo}instance.
     *
     * @param field the java.lang.reflect.Field object to find the annotation on.
     * @return a list with the annotations
     */
    public static List getAnnotationInfos(final Field field) {
        ClassLoader loader = field.getDeclaringClass().getClassLoader();
        ClassInfo classInfo = AsmClassInfo.getClassInfo(field.getDeclaringClass().getName(), loader);
        // AW fields like aw$instanceLevelAspects may not be visible
        FieldInfo fieldInfo = classInfo.getField(ReflectHelper.calculateHash(field));
        if (fieldInfo != null) {
            return fieldInfo.getAnnotations();
        } else {
            return EMPTY_LIST;
        }
    }

//    /**
//     * Returns the annotation proxy class for a specific annotation loaded in a specific loader.
//     *
//     * @param annotationName
//     * @param loader
//     * @return
//     */
//    public static Class getProxyClass(final String annotationName, final ClassLoader loader) {
//        Class proxyClass;
//        AsmClassInfoRepository classInfoRepository = AsmClassInfoRepository.getRepository(loader);
//        String proxyClassName = (String) classInfoRepository.getAnnotationProperties().get(annotationName);
//        if (proxyClassName == null) {
//            return null;
//        }
//        if (proxyClassName.equals("")) {
//            throw new DefinitionException("untyped annotations can not be used with Java5 annotations");
//        } else {
//            try {
//                proxyClass = loader.loadClass(proxyClassName);
//            } catch (ClassNotFoundException e) {
//                String message = proxyClassName
//                                 +
//                                 " could not be found on system classpath or class path provided as argument to the compiler";
//                throw new DefinitionException(message);
//            }
//        }
//        return proxyClass;
//    }

    private static String getAnnnotationName(Class annotation) {
        return annotation.getName().replace('/', '.');
    }
}