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; } } }