package ltd.beihu.core.tools.utils;

import ltd.beihu.core.tools.annotation.AnnotationAttributes;
import ltd.beihu.core.tools.annotation.BridgeMethodResolver;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationUtils {

    /**
     * The attribute name for annotations with a single element.
     */
    public static final String VALUE = "value";

    private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
            new ConcurrentHashMap<>(256);

    private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
            new ConcurrentHashMap<>(256);

    private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
            new ConcurrentHashMap<>(256);

    private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
            new ConcurrentHashMap<>(256);

    private static final Map<Class<? extends Annotation>, Map<String, List<String>>> attributeAliasesCache =
            new ConcurrentHashMap<>(256);

    private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache =
            new ConcurrentHashMap<>(256);

    private static transient Logger logger;


    /**
     * Get a single {@link Annotation} of {@code annotationType} from the supplied
     * annotation: either the given annotation itself or a direct meta-annotation
     * thereof.
     * <p>Note that this method supports only a single level of meta-annotations.
     * For support for arbitrary levels of meta-annotations, use one of the
     * {@code find*()} methods instead.
     *
     * @param ann            the Annotation to check
     * @param annotationType the annotation type to look for, both locally and as a meta-annotation
     * @return the first matching annotation, or {@code null} if not found
     * @since 4.0
     */
    @SuppressWarnings("unchecked")
    public static <A extends Annotation> A getAnnotation(Annotation ann, Class<A> annotationType) {
        if (annotationType.isInstance(ann)) {
            return (A) ann;
        }
        Class<? extends Annotation> annotatedElement = ann.annotationType();
        try {
            return findAnnotation(ann.getClass(), annotationType);
        } catch (Throwable ex) {
            handleIntrospectionFailure(annotatedElement, ex);
        }
        return null;
    }

    /**
     * Get a single {@link Annotation} of {@code annotationType} from the supplied
     * {@link AnnotatedElement}, where the annotation is either <em>present</em> or
     * <em>meta-present</em> on the {@code AnnotatedElement}.
     * <p>Note that this method supports only a single level of meta-annotations.
     * For support for arbitrary levels of meta-annotations, use
     * {@link #findAnnotation(AnnotatedElement, Class)} instead.
     *
     * @param annotatedElement the {@code AnnotatedElement} from which to get the annotation
     * @param annotationType   the annotation type to look for, both locally and as a meta-annotation
     * @return the first matching annotation, or {@code null} if not found
     * @since 3.1
     */
    public static <A extends Annotation> A getAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
        try {
            A annotation = annotatedElement.getAnnotation(annotationType);
            if (annotation == null) {
                for (Annotation metaAnn : annotatedElement.getAnnotations()) {
                    annotation = metaAnn.annotationType().getAnnotation(annotationType);
                    if (annotation != null) {
                        break;
                    }
                }
            }
            return annotation;
        } catch (Throwable ex) {
            handleIntrospectionFailure(annotatedElement, ex);
        }
        return null;
    }

    /**
     * Get a single {@link Annotation} of {@code annotationType} from the
     * supplied {@link Method}, where the annotation is either <em>present</em>
     * or <em>meta-present</em> on the method.
     * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
     * <p>Note that this method supports only a single level of meta-annotations.
     * For support for arbitrary levels of meta-annotations, use
     * {@link #findAnnotation(Method, Class)} instead.
     *
     * @param method         the method to look for annotations on
     * @param annotationType the annotation type to look for
     * @return the first matching annotation, or {@code null} if not found
     * @see #getAnnotation(AnnotatedElement, Class)
     */
    public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
        Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
        return getAnnotation((AnnotatedElement) resolvedMethod, annotationType);
    }

    /**
     * Get all {@link Annotation Annotations} that are <em>present</em> on the
     * supplied {@link AnnotatedElement}.
     * <p>Meta-annotations will <em>not</em> be searched.
     *
     * @param annotatedElement the Method, Constructor or Field to retrieve annotations from
     * @return the annotations found, an empty array, or {@code null} if not
     * resolvable (e.g. because nested Class values in annotation attributes
     * failed to resolve at runtime)
     * @see AnnotatedElement#getAnnotations()
     * @since 4.0.8
     */
    public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) {
        try {
            return annotatedElement.getAnnotations();
        } catch (Throwable ex) {
            handleIntrospectionFailure(annotatedElement, ex);
        }
        return null;
    }

    /**
     * Get all {@link Annotation Annotations} that are <em>present</em> on the
     * supplied {@link Method}.
     * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
     * <p>Meta-annotations will <em>not</em> be searched.
     *
     * @param method the Method to retrieve annotations from
     * @return the annotations found, an empty array, or {@code null} if not
     * resolvable (e.g. because nested Class values in annotation attributes
     * failed to resolve at runtime)
     * @see AnnotatedElement#getAnnotations()
     */
    public static Annotation[] getAnnotations(Method method) {
        try {
            return method.getAnnotations();
        } catch (Throwable ex) {
            handleIntrospectionFailure(method, ex);
        }
        return null;
    }


    /**
     * Get the <em>repeatable</em> {@linkplain Annotation annotations} of
     * {@code annotationType} from the supplied {@link AnnotatedElement}, where
     * such annotations are either <em>present</em>, <em>indirectly present</em>,
     * or <em>meta-present</em> on the element.
     * <p>This method mimics the functionality of Java 8's
     * {@link AnnotatedElement#getAnnotationsByType(Class)}
     * with support for automatic detection of a <em>container annotation</em>
     * declared via @{@link Repeatable} (when running on
     * Java 8 or higher) and with additional support for meta-annotations.
     * <p>Handles both single annotations and annotations nested within a
     * <em>container annotation</em>.
     * <p>Correctly handles <em>bridge methods</em> generated by the
     * compiler if the supplied element is a {@link Method}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>present</em> on the supplied element.
     *
     * @param annotatedElement the element to look for annotations on
     * @param annotationType   the annotation type to look for
     * @return the annotations found or an empty set (never {@code null})
     * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see Repeatable
     * @see AnnotatedElement#getAnnotationsByType
     * @since 4.2
     */
    public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
                                                                         Class<A> annotationType) {

        return getRepeatableAnnotations(annotatedElement, annotationType, null);
    }

    /**
     * Get the <em>repeatable</em> {@linkplain Annotation annotations} of
     * {@code annotationType} from the supplied {@link AnnotatedElement}, where
     * such annotations are either <em>present</em>, <em>indirectly present</em>,
     * or <em>meta-present</em> on the element.
     * <p>This method mimics the functionality of Java 8's
     * {@link AnnotatedElement#getAnnotationsByType(Class)}
     * with additional support for meta-annotations.
     * <p>Handles both single annotations and annotations nested within a
     * <em>container annotation</em>.
     * <p>Correctly handles <em>bridge methods</em> generated by the
     * compiler if the supplied element is a {@link Method}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>present</em> on the supplied element.
     *
     * @param annotatedElement        the element to look for annotations on
     * @param annotationType          the annotation type to look for
     * @param containerAnnotationType the type of the container that holds
     *                                the annotations; may be {@code null} if a container is not supported
     *                                or if it should be looked up via @{@link Repeatable}
     *                                when running on Java 8 or higher
     * @return the annotations found or an empty set (never {@code null})
     * @see #getRepeatableAnnotations(AnnotatedElement, Class)
     * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
     * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see Repeatable
     * @see AnnotatedElement#getAnnotationsByType
     * @since 4.2
     */
    public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
                                                                         Class<A> annotationType, Class<? extends Annotation> containerAnnotationType) {

        Set<A> annotations = getDeclaredRepeatableAnnotations(annotatedElement, annotationType, containerAnnotationType);
        if (!annotations.isEmpty()) {
            return annotations;
        }

        if (annotatedElement instanceof Class) {
            Class<?> superclass = ((Class<?>) annotatedElement).getSuperclass();
            if (superclass != null && Object.class != superclass) {
                return getRepeatableAnnotations(superclass, annotationType, containerAnnotationType);
            }
        }

        return getRepeatableAnnotations(annotatedElement, annotationType, containerAnnotationType, false);
    }

    /**
     * Get the declared <em>repeatable</em> {@linkplain Annotation annotations}
     * of {@code annotationType} from the supplied {@link AnnotatedElement},
     * where such annotations are either <em>directly present</em>,
     * <em>indirectly present</em>, or <em>meta-present</em> on the element.
     * <p>This method mimics the functionality of Java 8's
     * {@link AnnotatedElement#getDeclaredAnnotationsByType(Class)}
     * with support for automatic detection of a <em>container annotation</em>
     * declared via @{@link Repeatable} (when running on
     * Java 8 or higher) and with additional support for meta-annotations.
     * <p>Handles both single annotations and annotations nested within a
     * <em>container annotation</em>.
     * <p>Correctly handles <em>bridge methods</em> generated by the
     * compiler if the supplied element is a {@link Method}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>present</em> on the supplied element.
     *
     * @param annotatedElement the element to look for annotations on
     * @param annotationType   the annotation type to look for
     * @return the annotations found or an empty set (never {@code null})
     * @see #getRepeatableAnnotations(AnnotatedElement, Class)
     * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see Repeatable
     * @see AnnotatedElement#getDeclaredAnnotationsByType
     * @since 4.2
     */
    public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
                                                                                 Class<A> annotationType) {

        return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null);
    }

    /**
     * Get the declared <em>repeatable</em> {@linkplain Annotation annotations}
     * of {@code annotationType} from the supplied {@link AnnotatedElement},
     * where such annotations are either <em>directly present</em>,
     * <em>indirectly present</em>, or <em>meta-present</em> on the element.
     * <p>This method mimics the functionality of Java 8's
     * {@link AnnotatedElement#getDeclaredAnnotationsByType(Class)}
     * with additional support for meta-annotations.
     * <p>Handles both single annotations and annotations nested within a
     * <em>container annotation</em>.
     * <p>Correctly handles <em>bridge methods</em> generated by the
     * compiler if the supplied element is a {@link Method}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>present</em> on the supplied element.
     *
     * @param annotatedElement        the element to look for annotations on
     * @param annotationType          the annotation type to look for
     * @param containerAnnotationType the type of the container that holds
     *                                the annotations; may be {@code null} if a container is not supported
     *                                or if it should be looked up via @{@link Repeatable}
     *                                when running on Java 8 or higher
     * @return the annotations found or an empty set (never {@code null})
     * @see #getRepeatableAnnotations(AnnotatedElement, Class)
     * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
     * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
     * @see Repeatable
     * @see AnnotatedElement#getDeclaredAnnotationsByType
     * @since 4.2
     */
    public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
                                                                                 Class<A> annotationType, Class<? extends Annotation> containerAnnotationType) {

        return getRepeatableAnnotations(annotatedElement, annotationType, containerAnnotationType, true);
    }

    /**
     * Perform the actual work for {@link #getRepeatableAnnotations(AnnotatedElement, Class, Class)}
     * and {@link #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)}.
     * <p>Correctly handles <em>bridge methods</em> generated by the
     * compiler if the supplied element is a {@link Method}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>present</em> on the supplied element.
     *
     * @param annotatedElement        the element to look for annotations on
     * @param annotationType          the annotation type to look for
     * @param containerAnnotationType the type of the container that holds
     *                                the annotations; may be {@code null} if a container is not supported
     *                                or if it should be looked up via @{@link Repeatable}
     *                                when running on Java 8 or higher
     * @param declaredMode            {@code true} if only declared annotations (i.e.,
     *                                directly or indirectly present) should be considered
     * @return the annotations found or an empty set (never {@code null})
     * @see Repeatable
     * @since 4.2
     */
    private static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
                                                                          Class<A> annotationType, Class<? extends Annotation> containerAnnotationType, boolean declaredMode) {

        Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
        Assert.notNull(annotationType, "Annotation type must not be null");

        try {
            if (annotatedElement instanceof Method) {
                annotatedElement = BridgeMethodResolver.findBridgedMethod((Method) annotatedElement);
            }
            return new AnnotationCollector<>(annotationType, containerAnnotationType, declaredMode).getResult(annotatedElement);
        } catch (Throwable ex) {
            handleIntrospectionFailure(annotatedElement, ex);
        }
        return Collections.emptySet();
    }

    /**
     * Find a single {@link Annotation} of {@code annotationType} on the
     * supplied {@link AnnotatedElement}.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>directly present</em> on the supplied element.
     * <p><strong>Warning</strong>: this method operates generically on
     * annotated elements. In other words, this method does not execute
     * specialized search algorithms for classes or methods. If you require
     * the more specific semantics of {@link #findAnnotation(Class, Class)}
     * or {@link #findAnnotation(Method, Class)}, invoke one of those methods
     * instead.
     *
     * @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
     * @param annotationType   the annotation type to look for, both locally and as a meta-annotation
     * @return the first matching annotation, or {@code null} if not found
     * @since 4.2
     */
    public static <A extends Annotation> A findAnnotation(AnnotatedElement annotatedElement, Class<A> annotationType) {
        Assert.notNull(annotatedElement, "AnnotatedElement must not be null");
        if (annotationType == null) {
            return null;
        }

        // Do NOT store result in the findAnnotationCache since doing so could break
        // findAnnotation(Class, Class) and findAnnotation(Method, Class).
        A ann = findAnnotation(annotatedElement, annotationType, new HashSet<>());
        return ann;
    }

    /**
     * Perform the search algorithm for {@link #findAnnotation(AnnotatedElement, Class)}
     * avoiding endless recursion by tracking which annotations have already
     * been <em>visited</em>.
     *
     * @param annotatedElement the {@code AnnotatedElement} on which to find the annotation
     * @param annotationType   the annotation type to look for, both locally and as a meta-annotation
     * @param visited          the set of annotations that have already been visited
     * @return the first matching annotation, or {@code null} if not found
     * @since 4.2
     */
    @SuppressWarnings("unchecked")
    private static <A extends Annotation> A findAnnotation(
            AnnotatedElement annotatedElement, Class<A> annotationType, Set<Annotation> visited) {
        try {
            Annotation[] anns = annotatedElement.getDeclaredAnnotations();
            for (Annotation ann : anns) {
                if (ann.annotationType() == annotationType) {
                    return (A) ann;
                }
            }
            for (Annotation ann : anns) {
                if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
                    A annotation = findAnnotation((AnnotatedElement) ann.annotationType(), annotationType, visited);
                    if (annotation != null) {
                        return annotation;
                    }
                }
            }
        } catch (Throwable ex) {
            handleIntrospectionFailure(annotatedElement, ex);
        }
        return null;
    }

    /**
     * Find a single {@link Annotation} of {@code annotationType} on the supplied
     * {@link Method}, traversing its super methods (i.e., from superclasses and
     * interfaces) if the annotation is not <em>directly present</em> on the given
     * method itself.
     * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
     * <p>Meta-annotations will be searched if the annotation is not
     * <em>directly present</em> on the method.
     * <p>Annotations on methods are not inherited by default, so we need to handle
     * this explicitly.
     *
     * @param method         the method to look for annotations on
     * @param annotationType the annotation type to look for
     * @return the first matching annotation, or {@code null} if not found
     * @see #getAnnotation(Method, Class)
     */
    @SuppressWarnings("unchecked")
    public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
        Assert.notNull(method, "Method must not be null");
        if (annotationType == null) {
            return null;
        }

        AnnotationCacheKey cacheKey = new AnnotationCacheKey(method, annotationType);
        A result = (A) findAnnotationCache.get(cacheKey);

        if (result == null) {
            Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method);
            result = findAnnotation((AnnotatedElement) resolvedMethod, annotationType);

            if (result == null) {
                result = searchOnInterfaces(method, annotationType, method.getDeclaringClass().getInterfaces());
            }

            Class<?> clazz = method.getDeclaringClass();
            while (result == null) {
                clazz = clazz.getSuperclass();
                if (clazz == null || Object.class == clazz) {
                    break;
                }
                try {
                    Method equivalentMethod = clazz.getDeclaredMethod(method.getName(), method.getParameterTypes());
                    Method resolvedEquivalentMethod = BridgeMethodResolver.findBridgedMethod(equivalentMethod);
                    result = findAnnotation((AnnotatedElement) resolvedEquivalentMethod, annotationType);
                } catch (NoSuchMethodException ex) {
                    // No equivalent method found
                }
                if (result == null) {
                    result = searchOnInterfaces(method, annotationType, clazz.getInterfaces());
                }
            }

            if (result != null) {
                findAnnotationCache.put(cacheKey, result);
            }
        }

        return result;
    }

    private static <A extends Annotation> A searchOnInterfaces(Method method, Class<A> annotationType, Class<?>... ifcs) {
        A annotation = null;
        for (Class<?> iface : ifcs) {
            if (isInterfaceWithAnnotatedMethods(iface)) {
                try {
                    Method equivalentMethod = iface.getMethod(method.getName(), method.getParameterTypes());
                    annotation = getAnnotation(equivalentMethod, annotationType);
                } catch (NoSuchMethodException ex) {
                    // Skip this interface - it doesn't have the method...
                }
                if (annotation != null) {
                    break;
                }
            }
        }
        return annotation;
    }

    static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
        Boolean found = annotatedInterfaceCache.get(iface);
        if (found != null) {
            return found;
        }
        found = Boolean.FALSE;
        for (Method ifcMethod : iface.getMethods()) {
            try {
                if (ifcMethod.getAnnotations().length > 0) {
                    found = Boolean.TRUE;
                    break;
                }
            } catch (Throwable ex) {
                handleIntrospectionFailure(ifcMethod, ex);
            }
        }
        annotatedInterfaceCache.put(iface, found);
        return found;
    }

    /**
     * Find a single {@link Annotation} of {@code annotationType} on the
     * supplied {@link Class}, traversing its interfaces, annotations, and
     * superclasses if the annotation is not <em>directly present</em> on
     * the given class itself.
     * <p>This method explicitly handles class-level annotations which are not
     * declared as {@link java.lang.annotation.Inherited inherited} <em>as well
     * as meta-annotations and annotations on interfaces</em>.
     * <p>The algorithm operates as follows:
     * <ol>
     * <li>Search for the annotation on the given class and return it if found.
     * <li>Recursively search through all annotations that the given class declares.
     * <li>Recursively search through all interfaces that the given class declares.
     * <li>Recursively search through the superclass hierarchy of the given class.
     * </ol>
     * <p>Note: in this context, the term <em>recursively</em> means that the search
     * process continues by returning to step #1 with the current interface,
     * annotation, or superclass as the class to look for annotations on.
     *
     * @param clazz          the class to look for annotations on
     * @param annotationType the type of annotation to look for
     * @return the first matching annotation, or {@code null} if not found
     */
    public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
        return findAnnotation(clazz, annotationType, true);
    }

    /**
     * Perform the actual work for {@link #findAnnotation(AnnotatedElement, Class)},
     * honoring the {@code synthesize} flag.
     *
     * @param clazz          the class to look for annotations on
     * @param annotationType the type of annotation to look for
     * @return the first matching annotation, or {@code null} if not found
     * @since 4.2.1
     */
    @SuppressWarnings("unchecked")
    private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, boolean synthesize) {
        Assert.notNull(clazz, "Class must not be null");
        if (annotationType == null) {
            return null;
        }

        AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
        A result = (A) findAnnotationCache.get(cacheKey);
        if (result == null) {
            result = findAnnotation(clazz, annotationType, new HashSet<>());
            if (result != null && synthesize) {
                findAnnotationCache.put(cacheKey, result);
            }
        }
        return result;
    }

    /**
     * Perform the search algorithm for {@link #findAnnotation(Class, Class)},
     * avoiding endless recursion by tracking which annotations have already
     * been <em>visited</em>.
     *
     * @param clazz          the class to look for annotations on
     * @param annotationType the type of annotation to look for
     * @param visited        the set of annotations that have already been visited
     * @return the first matching annotation, or {@code null} if not found
     */
    @SuppressWarnings("unchecked")
    private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, Set<Annotation> visited) {
        try {
            Annotation[] anns = clazz.getDeclaredAnnotations();
            for (Annotation ann : anns) {
                if (ann.annotationType() == annotationType) {
                    return (A) ann;
                }
            }
            for (Annotation ann : anns) {
                if (!isInJavaLangAnnotationPackage(ann) && visited.add(ann)) {
                    A annotation = findAnnotation(ann.annotationType(), annotationType, visited);
                    if (annotation != null) {
                        return annotation;
                    }
                }
            }
        } catch (Throwable ex) {
            handleIntrospectionFailure(clazz, ex);
            return null;
        }

        for (Class<?> ifc : clazz.getInterfaces()) {
            A annotation = findAnnotation(ifc, annotationType, visited);
            if (annotation != null) {
                return annotation;
            }
        }

        Class<?> superclass = clazz.getSuperclass();
        if (superclass == null || Object.class == superclass) {
            return null;
        }
        return findAnnotation(superclass, annotationType, visited);
    }

    /**
     * Find the first {@link Class} in the inheritance hierarchy of the
     * specified {@code clazz} (including the specified {@code clazz} itself)
     * on which an annotation of the specified {@code annotationType} is
     * <em>directly present</em>.
     * <p>If the supplied {@code clazz} is an interface, only the interface
     * itself will be checked; the inheritance hierarchy for interfaces will
     * not be traversed.
     * <p>Meta-annotations will <em>not</em> be searched.
     * <p>The standard {@link Class} API does not provide a mechanism for
     * determining which class in an inheritance hierarchy actually declares
     * an {@link Annotation}, so we need to handle this explicitly.
     *
     * @param annotationType the annotation type to look for
     * @param clazz          the class to check for the annotation on (may be {@code null})
     * @return the first {@link Class} in the inheritance hierarchy that
     * declares an annotation of the specified {@code annotationType}, or
     * {@code null} if not found
     * @see Class#isAnnotationPresent(Class)
     * @see Class#getDeclaredAnnotations()
     * @see #findAnnotationDeclaringClassForTypes(List, Class)
     * @see #isAnnotationDeclaredLocally(Class, Class)
     */
    public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, Class<?> clazz) {
        Assert.notNull(annotationType, "Annotation type must not be null");
        if (clazz == null || Object.class == clazz) {
            return null;
        }
        if (isAnnotationDeclaredLocally(annotationType, clazz)) {
            return clazz;
        }
        return findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
    }

    /**
     * Find the first {@link Class} in the inheritance hierarchy of the
     * specified {@code clazz} (including the specified {@code clazz} itself)
     * on which at least one of the specified {@code annotationTypes} is
     * <em>directly present</em>.
     * <p>If the supplied {@code clazz} is an interface, only the interface
     * itself will be checked; the inheritance hierarchy for interfaces will
     * not be traversed.
     * <p>Meta-annotations will <em>not</em> be searched.
     * <p>The standard {@link Class} API does not provide a mechanism for
     * determining which class in an inheritance hierarchy actually declares
     * one of several candidate {@linkplain Annotation annotations}, so we
     * need to handle this explicitly.
     *
     * @param annotationTypes the annotation types to look for
     * @param clazz           the class to check for the annotations on, or {@code null}
     * @return the first {@link Class} in the inheritance hierarchy that
     * declares an annotation of at least one of the specified
     * {@code annotationTypes}, or {@code null} if not found
     * @see Class#isAnnotationPresent(Class)
     * @see Class#getDeclaredAnnotations()
     * @see #findAnnotationDeclaringClass(Class, Class)
     * @see #isAnnotationDeclaredLocally(Class, Class)
     * @since 3.2.2
     */
    public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends Annotation>> annotationTypes, Class<?> clazz) {
        Assert.notEmpty(annotationTypes, "List of annotation types must not be empty");
        if (clazz == null || Object.class == clazz) {
            return null;
        }
        for (Class<? extends Annotation> annotationType : annotationTypes) {
            if (isAnnotationDeclaredLocally(annotationType, clazz)) {
                return clazz;
            }
        }
        return findAnnotationDeclaringClassForTypes(annotationTypes, clazz.getSuperclass());
    }

    /**
     * Determine whether an annotation of the specified {@code annotationType}
     * is declared locally (i.e., <em>directly present</em>) on the supplied
     * {@code clazz}.
     * <p>The supplied {@link Class} may represent any type.
     * <p>Meta-annotations will <em>not</em> be searched.
     * <p>Note: This method does <strong>not</strong> determine if the annotation
     * is {@linkplain java.lang.annotation.Inherited inherited}. For greater
     * clarity regarding inherited annotations, consider using
     * {@link #isAnnotationInherited(Class, Class)} instead.
     *
     * @param annotationType the annotation type to look for
     * @param clazz          the class to check for the annotation on
     * @return {@code true} if an annotation of the specified {@code annotationType}
     * is <em>directly present</em>
     * @see Class#getDeclaredAnnotations()
     * @see Class#getDeclaredAnnotation(Class)
     * @see #isAnnotationInherited(Class, Class)
     */
    public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
        Assert.notNull(annotationType, "Annotation type must not be null");
        Assert.notNull(clazz, "Class must not be null");
        try {
            for (Annotation ann : clazz.getDeclaredAnnotations()) {
                if (ann.annotationType() == annotationType) {
                    return true;
                }
            }
        } catch (Throwable ex) {
            handleIntrospectionFailure(clazz, ex);
        }
        return false;
    }

    /**
     * Determine whether an annotation of the specified {@code annotationType}
     * is <em>present</em> on the supplied {@code clazz} and is
     * {@linkplain java.lang.annotation.Inherited inherited} (i.e., not
     * <em>directly present</em>).
     * <p>Meta-annotations will <em>not</em> be searched.
     * <p>If the supplied {@code clazz} is an interface, only the interface
     * itself will be checked. In accordance with standard meta-annotation
     * semantics in Java, the inheritance hierarchy for interfaces will not
     * be traversed. See the {@linkplain java.lang.annotation.Inherited Javadoc}
     * for the {@code @Inherited} meta-annotation for further details regarding
     * annotation inheritance.
     *
     * @param annotationType the annotation type to look for
     * @param clazz          the class to check for the annotation on
     * @return {@code true} if an annotation of the specified {@code annotationType}
     * is <em>present</em> and <em>inherited</em>
     * @see Class#isAnnotationPresent(Class)
     * @see #isAnnotationDeclaredLocally(Class, Class)
     */
    public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
        Assert.notNull(annotationType, "Annotation type must not be null");
        Assert.notNull(clazz, "Class must not be null");
        return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
    }

    /**
     * Determine if an annotation of type {@code metaAnnotationType} is
     * <em>meta-present</em> on the supplied {@code annotationType}.
     *
     * @param annotationType     the annotation type to search on
     * @param metaAnnotationType the type of meta-annotation to search for
     * @return {@code true} if such an annotation is meta-present
     * @since 4.2.1
     */
    public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
                                                  Class<? extends Annotation> metaAnnotationType) {

        Assert.notNull(annotationType, "Annotation type must not be null");
        if (metaAnnotationType == null) {
            return false;
        }

        AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
        Boolean metaPresent = metaPresentCache.get(cacheKey);
        if (metaPresent != null) {
            return metaPresent;
        }
        metaPresent = Boolean.FALSE;
        if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
            metaPresent = Boolean.TRUE;
        }
        metaPresentCache.put(cacheKey, metaPresent);
        return metaPresent;
    }

    /**
     * Determine if the supplied {@link Annotation} is defined in the core JDK
     * {@code java.lang.annotation} package.
     *
     * @param annotation the annotation to check
     * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
     */
    public static boolean isInJavaLangAnnotationPackage(Annotation annotation) {
        return (annotation != null && isInJavaLangAnnotationPackage(annotation.annotationType().getName()));
    }

    /**
     * Determine if the {@link Annotation} with the supplied name is defined
     * in the core JDK {@code java.lang.annotation} package.
     *
     * @param annotationType the name of the annotation type to check (never {@code null} or empty)
     * @return {@code true} if the annotation is in the {@code java.lang.annotation} package
     * @since 4.2
     */
    public static boolean isInJavaLangAnnotationPackage(String annotationType) {
        return (annotationType != null && annotationType.startsWith("java.lang.annotation"));
    }

    /**
     * Retrieve the given annotation's attributes as a {@link Map}, preserving all
     * attribute types.
     * with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters
     * set to {@code false}.
     * <p>Note: This method actually returns an {@link AnnotationAttributes} instance.
     * However, the {@code Map} signature has been preserved for binary compatibility.
     *
     * @param annotation the annotation to retrieve the attributes for
     * @return the Map of annotation attributes, with attribute names as keys and
     * corresponding attribute values as values (never {@code null})
     * @see #getAnnotationAttributes(AnnotatedElement, Annotation)
     */
    public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
        return getAnnotationAttributes(null, annotation);
    }

    /**
     * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
     * with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters
     * set to {@code false}.
     *
     * @param annotatedElement the element that is annotated with the supplied annotation;
     *                         may be {@code null} if unknown
     * @param annotation       the annotation to retrieve the attributes for
     * @return the annotation attributes (a specialized Map) with attribute names as keys
     * and corresponding attribute values as values (never {@code null})
     * @since 4.2
     */
    public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation) {
        return getAnnotationAttributes(annotatedElement, annotation, false);
    }

    /**
     * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
     * <p>This method provides fully recursive annotation reading capabilities on par with
     *
     * @param annotatedElement       the element that is annotated with the supplied annotation;
     *                               may be {@code null} if unknown
     * @param annotation             the annotation to retrieve the attributes for
     * @return the annotation attributes (a specialized Map) with attribute names as keys
     * and corresponding attribute values as values (never {@code null})
     * @since 4.2
     */
    public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
                                                               Annotation annotation, boolean classValuesAsString) {

        return getAnnotationAttributes(
                (Object) annotatedElement, annotation, classValuesAsString);
    }

    private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement,
                                                                Annotation annotation, boolean classValuesAsString) {

        AnnotationAttributes attributes =
                retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString);
        postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString);
        return attributes;
    }

    /**
     * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map.
     * <p>This method provides fully recursive annotation reading capabilities on par with
     * <p><strong>NOTE</strong>: This variant of {@code getAnnotationAttributes()} is
     * only intended for use within the framework. The following special rules apply:
     * <ol>
     * <li>Default values will be replaced with default value placeholders.</li>
     * <li>The resulting, merged annotation attributes should eventually be
     * {@linkplain #postProcessAnnotationAttributes post-processed} in order to
     * ensure that placeholders have been replaced by actual default values and
     * in order to enforce {@code @AliasFor} semantics.</li>
     * </ol>
     *
     * @param annotatedElement       the element that is annotated with the supplied annotation;
     *                               may be {@code null} if unknown
     * @param annotation             the annotation to retrieve the attributes for
     * @param classValuesAsString    whether to convert Class references into Strings (for
     *                               or to preserve them as Class references
     * @return the annotation attributes (a specialized Map) with attribute names as keys
     * and corresponding attribute values as values (never {@code null})
     * @see #postProcessAnnotationAttributes
     * @since 4.2
     */
    static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
                                                             boolean classValuesAsString) {

        Class<? extends Annotation> annotationType = annotation.annotationType();
        AnnotationAttributes attributes = new AnnotationAttributes(annotationType);

        for (Method method : getAttributeMethods(annotationType)) {
            try {
                Object attributeValue = method.invoke(annotation);
                Object defaultValue = method.getDefaultValue();
                if (defaultValue != null && Objects.equals(attributeValue, defaultValue)) {
                    attributeValue = new DefaultValueHolder(defaultValue);
                }
                attributes.put(method.getName(),
                        adaptValue(annotatedElement, attributeValue, classValuesAsString));
            } catch (Throwable ex) {
                if (ex instanceof InvocationTargetException) {
                    Throwable targetException = ((InvocationTargetException) ex).getTargetException();
                    rethrowAnnotationConfigurationException(targetException);
                }
                throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex);
            }
        }

        return attributes;
    }

    /**
     * Adapt the given value according to the given class and nested annotation settings.
     * <p>Nested annotations will be
     *
     * @param annotatedElement       the element that is annotated, used for contextual
     *                               logging; may be {@code null} if unknown
     * @param value                  the annotation attribute value
     * @param classValuesAsString    whether to convert Class references into Strings (for
     *                               or to preserve them as Class references
     * @return the adapted value, or the original value if no adaptation is needed
     */
    static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString) {

        if (classValuesAsString) {
            if (value instanceof Class) {
                return ((Class<?>) value).getName();
            } else if (value instanceof Class[]) {
                Class<?>[] clazzArray = (Class<?>[]) value;
                String[] classNames = new String[clazzArray.length];
                for (int i = 0; i < clazzArray.length; i++) {
                    classNames[i] = clazzArray[i].getName();
                }
                return classNames;
            }
        }

        if (value instanceof Annotation) {
            Annotation annotation = (Annotation) value;
            return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString);

        }

        if (value instanceof Annotation[]) {
            Annotation[] annotations = (Annotation[]) value;
            AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length];
            for (int i = 0; i < annotations.length; i++) {
                mappedAnnotations[i] =
                        getAnnotationAttributes(annotatedElement, annotations[i], classValuesAsString);
            }
            return mappedAnnotations;

        }

        // Fallback
        return value;
    }

    /**
     * Post-process the supplied {@link AnnotationAttributes}.
     * <p>Specifically, this method enforces <em>attribute alias</em> semantics
     * and replaces default value placeholders with their original default values.
     *
     * @param annotatedElement       the element that is annotated with an annotation or
     *                               annotation hierarchy from which the supplied attributes were created;
     *                               may be {@code null} if unknown
     * @param attributes             the annotation attributes to post-process
     * @param classValuesAsString    whether to convert Class references into Strings (for
     *                               or to preserve them as Class references
     * @see #getDefaultValue(Class, String)
     * @since 4.2
     */
    static void postProcessAnnotationAttributes(Object annotatedElement,
                                                AnnotationAttributes attributes, boolean classValuesAsString) {

        // Abort?
        if (attributes == null) {
            return;
        }

        Class<? extends Annotation> annotationType = attributes.annotationType();

        // Replace any remaining placeholders with actual default values
        for (String attributeName : attributes.keySet()) {
            Object value = attributes.get(attributeName);
            if (value instanceof DefaultValueHolder) {
                value = ((DefaultValueHolder) value).defaultValue;
                attributes.put(attributeName,
                        adaptValue(annotatedElement, value, classValuesAsString));
            }
        }
    }

    /**
     * Retrieve the <em>value</em> of the {@code value} attribute of a
     * single-element Annotation, given an annotation instance.
     *
     * @param annotation the annotation instance from which to retrieve the value
     * @return the attribute value, or {@code null} if not found
     * @see #getValue(Annotation, String)
     */
    public static Object getValue(Annotation annotation) {
        return getValue(annotation, VALUE);
    }

    /**
     * Retrieve the <em>value</em> of a named attribute, given an annotation instance.
     *
     * @param annotation    the annotation instance from which to retrieve the value
     * @param attributeName the name of the attribute value to retrieve
     * @return the attribute value, or {@code null} if not found
     * @see #getValue(Annotation)
     */
    public static Object getValue(Annotation annotation, String attributeName) {
        if (annotation == null || !StringUtils.isNotBlank(attributeName)) {
            return null;
        }
        try {
            Method method = annotation.annotationType().getDeclaredMethod(attributeName);
            ReflectionHelper.makeAccessible(method);
            return method.invoke(annotation);
        } catch (Exception ex) {
            return null;
        }
    }

    /**
     * Retrieve the <em>default value</em> of the {@code value} attribute
     * of a single-element Annotation, given an annotation instance.
     *
     * @param annotation the annotation instance from which to retrieve the default value
     * @return the default value, or {@code null} if not found
     * @see #getDefaultValue(Annotation, String)
     */
    public static Object getDefaultValue(Annotation annotation) {
        return getDefaultValue(annotation, VALUE);
    }

    /**
     * Retrieve the <em>default value</em> of a named attribute, given an annotation instance.
     *
     * @param annotation    the annotation instance from which to retrieve the default value
     * @param attributeName the name of the attribute value to retrieve
     * @return the default value of the named attribute, or {@code null} if not found
     * @see #getDefaultValue(Class, String)
     */
    public static Object getDefaultValue(Annotation annotation, String attributeName) {
        if (annotation == null) {
            return null;
        }
        return getDefaultValue(annotation.annotationType(), attributeName);
    }

    /**
     * Retrieve the <em>default value</em> of the {@code value} attribute
     * of a single-element Annotation, given the {@link Class annotation type}.
     *
     * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
     * @return the default value, or {@code null} if not found
     * @see #getDefaultValue(Class, String)
     */
    public static Object getDefaultValue(Class<? extends Annotation> annotationType) {
        return getDefaultValue(annotationType, VALUE);
    }

    /**
     * Retrieve the <em>default value</em> of a named attribute, given the
     * {@link Class annotation type}.
     *
     * @param annotationType the <em>annotation type</em> for which the default value should be retrieved
     * @param attributeName  the name of the attribute value to retrieve.
     * @return the default value of the named attribute, or {@code null} if not found
     * @see #getDefaultValue(Annotation, String)
     */
    public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
        if (annotationType == null || !StringUtils.isNotBlank(attributeName)) {
            return null;
        }
        try {
            return annotationType.getDeclaredMethod(attributeName).getDefaultValue();
        } catch (Exception ex) {
            return null;
        }
    }


    /**
     * Get all methods declared in the supplied {@code annotationType} that
     * match Java's requirements for annotation <em>attributes</em>.
     * <p>All methods in the returned list will be
     * {@linkplain ReflectionHelper#makeAccessible(Method) made accessible}.
     *
     * @param annotationType the type in which to search for attribute methods;
     *                       never {@code null}
     * @return all annotation attribute methods in the specified annotation
     * type (never {@code null}, though potentially <em>empty</em>)
     * @since 4.2
     */
    static List<Method> getAttributeMethods(Class<? extends Annotation> annotationType) {
        List<Method> methods = attributeMethodsCache.get(annotationType);
        if (methods != null) {
            return methods;
        }

        methods = new ArrayList<>();
        for (Method method : annotationType.getDeclaredMethods()) {
            if (isAttributeMethod(method)) {
                ReflectionHelper.makeAccessible(method);
                methods.add(method);
            }
        }

        attributeMethodsCache.put(annotationType, methods);
        return methods;
    }

    /**
     * Get the annotation with the supplied {@code annotationName} on the
     * supplied {@code element}.
     *
     * @param element        the element to search on
     * @param annotationName the fully qualified class name of the annotation
     *                       type to find
     * @return the annotation if found; {@code null} otherwise
     * @since 4.2
     */
    static Annotation getAnnotation(AnnotatedElement element, String annotationName) {
        for (Annotation annotation : element.getAnnotations()) {
            if (annotation.annotationType().getName().equals(annotationName)) {
                return annotation;
            }
        }
        return null;
    }

    /**
     * Determine if the supplied {@code method} is an annotation attribute method.
     *
     * @param method the method to check
     * @return {@code true} if the method is an attribute method
     * @since 4.2
     */
    static boolean isAttributeMethod(Method method) {
        return (method != null && method.getParameterCount() == 0 && method.getReturnType() != void.class);
    }

    /**
     * Determine if the supplied method is an "annotationType" method.
     *
     * @return {@code true} if the method is an "annotationType" method
     * @see Annotation#annotationType()
     * @since 4.2
     */
    static boolean isAnnotationTypeMethod(Method method) {
        return (method != null && method.getName().equals("annotationType") && method.getParameterCount() == 0);
    }

    /**
     * Resolve the container type for the supplied repeatable {@code annotationType}.
     * <p>Automatically detects a <em>container annotation</em> declared via
     * {@link Repeatable}. If the supplied annotation type
     * is not annotated with {@code @Repeatable}, this method simply returns
     * {@code null}.
     *
     * @since 4.2
     */
    static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
        Repeatable repeatable = getAnnotation(annotationType, Repeatable.class);
        return (repeatable != null ? repeatable.value() : null);
    }

    /**
     * If the supplied throwable is an {@link},
     * it will be cast to an {@code AnnotationConfigurationException} and thrown,
     * allowing it to propagate to the caller.
     * <p>Otherwise, this method does nothing.
     *
     * @param ex the throwable to inspect
     * @since 4.2
     */
    static void rethrowAnnotationConfigurationException(Throwable ex) {
    }

    /**
     * Handle the supplied annotation introspection exception.
     * it will simply be thrown, allowing it to propagate to the caller, and
     * nothing will be logged.
     * <p>Otherwise, this method logs an introspection failure (in particular
     * {@code TypeNotPresentExceptions}) before moving on, assuming nested
     * Class values were not resolvable within annotation attributes and
     * thereby effectively pretending there were no annotations on the specified
     * element.
     *
     * @param element the element that we tried to introspect annotations on
     * @param ex      the exception that we encountered
     * @see #rethrowAnnotationConfigurationException
     */
    static void handleIntrospectionFailure(AnnotatedElement element, Throwable ex) {
        rethrowAnnotationConfigurationException(ex);

        Logger loggerToUse = logger;
        if (loggerToUse == null) {
            loggerToUse = LoggerFactory.getLogger(AnnotationUtils.class);
            logger = loggerToUse;
        }
        if (element instanceof Class && Annotation.class.isAssignableFrom((Class<?>) element)) {
            // Meta-annotation lookup on an annotation type
            if (loggerToUse.isDebugEnabled()) {
                loggerToUse.debug("Failed to introspect meta-annotations on [" + element + "]: " + ex);
            }
        } else {
            // Direct annotation lookup on regular Class, Method, Field
            if (loggerToUse.isInfoEnabled()) {
                loggerToUse.info("Failed to introspect annotations on [" + element + "]: " + ex);
            }
        }
    }


    /**
     * Cache key for the AnnotatedElement cache.
     */
    private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> {

        private final AnnotatedElement element;

        private final Class<? extends Annotation> annotationType;

        public AnnotationCacheKey(AnnotatedElement element, Class<? extends Annotation> annotationType) {
            this.element = element;
            this.annotationType = annotationType;
        }

        @Override
        public boolean equals(Object other) {
            if (this == other) {
                return true;
            }
            if (!(other instanceof AnnotationCacheKey)) {
                return false;
            }
            AnnotationCacheKey otherKey = (AnnotationCacheKey) other;
            return (this.element.equals(otherKey.element) && this.annotationType.equals(otherKey.annotationType));
        }

        @Override
        public int hashCode() {
            return (this.element.hashCode() * 29 + this.annotationType.hashCode());
        }

        @Override
        public String toString() {
            return "@" + this.annotationType + " on " + this.element;
        }

        @Override
        public int compareTo(AnnotationCacheKey other) {
            int result = this.element.toString().compareTo(other.element.toString());
            if (result == 0) {
                result = this.annotationType.getName().compareTo(other.annotationType.getName());
            }
            return result;
        }
    }


    private static class AnnotationCollector<A extends Annotation> {

        private final Class<A> annotationType;

        private final Class<? extends Annotation> containerAnnotationType;

        private final boolean declaredMode;

        private final Set<AnnotatedElement> visited = new HashSet<>();

        private final Set<A> result = new LinkedHashSet<>();

        AnnotationCollector(Class<A> annotationType, Class<? extends Annotation> containerAnnotationType, boolean declaredMode) {
            this.annotationType = annotationType;
            this.containerAnnotationType = (containerAnnotationType != null ? containerAnnotationType :
                    resolveContainerAnnotationType(annotationType));
            this.declaredMode = declaredMode;
        }

        Set<A> getResult(AnnotatedElement element) {
            process(element);
            return Collections.unmodifiableSet(this.result);
        }

        @SuppressWarnings("unchecked")
        private void process(AnnotatedElement element) {
            if (this.visited.add(element)) {
                try {
                    Annotation[] annotations = (this.declaredMode ? element.getDeclaredAnnotations() : element.getAnnotations());
                    for (Annotation ann : annotations) {
                        Class<? extends Annotation> currentAnnotationType = ann.annotationType();
                        if (Objects.equals(this.annotationType, currentAnnotationType)) {
                            this.result.add((A) ann);
                        } else if (Objects.equals(this.containerAnnotationType, currentAnnotationType)) {
                            this.result.addAll(getValue(element, ann));
                        } else if (!isInJavaLangAnnotationPackage(ann)) {
                            process(currentAnnotationType);
                        }
                    }
                } catch (Throwable ex) {
                    handleIntrospectionFailure(element, ex);
                }
            }
        }

        @SuppressWarnings("unchecked")
        private List<A> getValue(AnnotatedElement element, Annotation annotation) {
            try {
                return new ArrayList<>(Arrays.asList((A[]) AnnotationUtils.getValue(annotation)));
            } catch (Throwable ex) {
                handleIntrospectionFailure(element, ex);
            }
            // Unable to read value from repeating annotation container -> ignore it.
            return Collections.emptyList();
        }
    }


    private static class DefaultValueHolder {

        final Object defaultValue;

        public DefaultValueHolder(Object defaultValue) {
            this.defaultValue = defaultValue;
        }
    }
}