All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.core.annotation.AnnotationUtils Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2019 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

/**
 * General utility methods for working with annotations, handling meta-annotations,
 * bridge methods (which the compiler generates for generic declarations) as well
 * as super methods (for optional annotation inheritance).
 *
 * 

Note that most of the features of this class are not provided by the * JDK's introspection facilities themselves. * *

As a general rule for runtime-retained application annotations (e.g. for * transaction control, authorization, or service exposure), always use the * lookup methods on this class (e.g. {@link #findAnnotation(Method, Class)} or * {@link #getAnnotation(Method, Class)}) instead of the plain annotation lookup * methods in the JDK. You can still explicitly choose between a get * lookup on the given class level only ({@link #getAnnotation(Method, Class)}) * and a find lookup in the entire inheritance hierarchy of the given * method ({@link #findAnnotation(Method, Class)}). * *

Terminology

* The terms directly present, indirectly present, and * present have the same meanings as defined in the class-level * javadoc for {@link AnnotatedElement} (in Java 8). * *

An annotation is meta-present on an element if the annotation * is declared as a meta-annotation on some other annotation which is * present on the element. Annotation {@code A} is meta-present * on another annotation if {@code A} is either directly present or * meta-present on the other annotation. * *

Meta-annotation Support

*

Most {@code find*()} methods and some {@code get*()} methods in this class * provide support for finding annotations used as meta-annotations. Consult the * javadoc for each method in this class for details. For fine-grained support for * meta-annotations with attribute overrides in composed annotations, * consider using {@link AnnotatedElementUtils}'s more specific methods instead. * *

Attribute Aliases

*

All public methods in this class that return annotations, arrays of * annotations, or {@link AnnotationAttributes} transparently support attribute * aliases configured via {@link AliasFor @AliasFor}. Consult the various * {@code synthesizeAnnotation*(..)} methods for details. * *

Search Scope

*

The search algorithms used by methods in this class stop searching for * an annotation once the first annotation of the specified type has been * found. As a consequence, additional annotations of the specified type will * be silently ignored. * * @author Rob Harrop * @author Juergen Hoeller * @author Sam Brannen * @author Mark Fisher * @author Chris Beams * @author Phillip Webb * @author Oleg Zhurakousky * @since 2.0 * @see AliasFor * @see AnnotationAttributes * @see AnnotatedElementUtils * @see BridgeMethodResolver * @see java.lang.reflect.AnnotatedElement#getAnnotations() * @see java.lang.reflect.AnnotatedElement#getAnnotation(Class) * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotations() */ public abstract class AnnotationUtils { /** * The attribute name for annotations with a single element. */ public static final String VALUE = "value"; private static final Map findAnnotationCache = new ConcurrentReferenceHashMap<>(256); private static final Map metaPresentCache = new ConcurrentReferenceHashMap<>(256); private static final Map declaredAnnotationsCache = new ConcurrentReferenceHashMap<>(256); private static final Map, Set> annotatedBaseTypeCache = new ConcurrentReferenceHashMap<>(256); @SuppressWarnings("unused") @Deprecated // just here for older tool versions trying to reflectively clear the cache private static final Map, ?> annotatedInterfaceCache = annotatedBaseTypeCache; private static final Map, Boolean> synthesizableCache = new ConcurrentReferenceHashMap<>(256); private static final Map, Map>> attributeAliasesCache = new ConcurrentReferenceHashMap<>(256); private static final Map, List> attributeMethodsCache = new ConcurrentReferenceHashMap<>(256); private static final Map aliasDescriptorCache = new ConcurrentReferenceHashMap<>(256); @Nullable private static transient Log logger; /** * Get a single {@link Annotation} of {@code annotationType} from the supplied * annotation: either the given annotation itself or a direct meta-annotation * thereof. *

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 annotation 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") @Nullable public static A getAnnotation(Annotation annotation, Class annotationType) { if (annotationType.isInstance(annotation)) { return synthesizeAnnotation((A) annotation); } Class annotatedElement = annotation.annotationType(); try { A metaAnn = annotatedElement.getAnnotation(annotationType); return (metaAnn != null ? synthesizeAnnotation(metaAnn, annotatedElement) : null); } 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 present or * meta-present on the {@code AnnotatedElement}. *

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 */ @Nullable public static A getAnnotation(AnnotatedElement annotatedElement, Class 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 != null ? synthesizeAnnotation(annotation, annotatedElement) : null); } 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 present * or meta-present on the method. *

Correctly handles bridge {@link Method Methods} generated by the compiler. *

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 org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) * @see #getAnnotation(AnnotatedElement, Class) */ @Nullable public static A getAnnotation(Method method, Class annotationType) { Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); } /** * Get all {@link Annotation Annotations} that are present on the * supplied {@link AnnotatedElement}. *

Meta-annotations will not 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) * @since 4.0.8 * @see AnnotatedElement#getAnnotations() */ @Nullable public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { try { return synthesizeAnnotationArray(annotatedElement.getAnnotations(), annotatedElement); } catch (Throwable ex) { handleIntrospectionFailure(annotatedElement, ex); return null; } } /** * Get all {@link Annotation Annotations} that are present on the * supplied {@link Method}. *

Correctly handles bridge {@link Method Methods} generated by the compiler. *

Meta-annotations will not 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 org.springframework.core.BridgeMethodResolver#findBridgedMethod(Method) * @see AnnotatedElement#getAnnotations() */ @Nullable public static Annotation[] getAnnotations(Method method) { try { return synthesizeAnnotationArray(BridgeMethodResolver.findBridgedMethod(method).getAnnotations(), method); } catch (Throwable ex) { handleIntrospectionFailure(method, ex); return null; } } /** * Get the repeatable {@linkplain Annotation annotations} of * {@code annotationType} from the supplied {@link AnnotatedElement}, where * such annotations are either present, indirectly present, * or meta-present on the element. *

This method mimics the functionality of Java 8's * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} * with support for automatic detection of a container annotation * declared via @{@link java.lang.annotation.Repeatable} (when running on * Java 8 or higher) and with additional support for meta-annotations. *

Handles both single annotations and annotations nested within a * container annotation. *

Correctly handles bridge methods generated by the * compiler if the supplied element is a {@link Method}. *

Meta-annotations will be searched if the annotation is not * present 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}) * @since 4.2 * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType */ public static Set getRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType) { return getRepeatableAnnotations(annotatedElement, annotationType, null); } /** * Get the repeatable {@linkplain Annotation annotations} of * {@code annotationType} from the supplied {@link AnnotatedElement}, where * such annotations are either present, indirectly present, * or meta-present on the element. *

This method mimics the functionality of Java 8's * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} * with additional support for meta-annotations. *

Handles both single annotations and annotations nested within a * container annotation. *

Correctly handles bridge methods generated by the * compiler if the supplied element is a {@link Method}. *

Meta-annotations will be searched if the annotation is not * present 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 java.lang.annotation.Repeatable} * when running on Java 8 or higher * @return the annotations found or an empty set (never {@code null}) * @since 4.2 * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getAnnotationsByType */ public static Set getRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType, @Nullable Class containerAnnotationType) { Set annotations = getDeclaredRepeatableAnnotations(annotatedElement, annotationType, containerAnnotationType); if (annotations.isEmpty() && annotatedElement instanceof Class) { Class superclass = ((Class) annotatedElement).getSuperclass(); if (superclass != null && superclass != Object.class) { return getRepeatableAnnotations(superclass, annotationType, containerAnnotationType); } } return annotations; } /** * Get the declared repeatable {@linkplain Annotation annotations} * of {@code annotationType} from the supplied {@link AnnotatedElement}, * where such annotations are either directly present, * indirectly present, or meta-present on the element. *

This method mimics the functionality of Java 8's * {@link java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType(Class)} * with support for automatic detection of a container annotation * declared via @{@link java.lang.annotation.Repeatable} (when running on * Java 8 or higher) and with additional support for meta-annotations. *

Handles both single annotations and annotations nested within a * container annotation. *

Correctly handles bridge methods generated by the * compiler if the supplied element is a {@link Method}. *

Meta-annotations will be searched if the annotation is not * present 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}) * @since 4.2 * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class) * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType */ public static Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType) { return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null); } /** * Get the declared repeatable {@linkplain Annotation annotations} * of {@code annotationType} from the supplied {@link AnnotatedElement}, * where such annotations are either directly present, * indirectly present, or meta-present on the element. *

This method mimics the functionality of Java 8's * {@link java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType(Class)} * with additional support for meta-annotations. *

Handles both single annotations and annotations nested within a * container annotation. *

Correctly handles bridge methods generated by the * compiler if the supplied element is a {@link Method}. *

Meta-annotations will be searched if the annotation is not * present 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 java.lang.annotation.Repeatable} * when running on Java 8 or higher * @return the annotations found or an empty set (never {@code null}) * @since 4.2 * @see #getRepeatableAnnotations(AnnotatedElement, Class) * @see #getRepeatableAnnotations(AnnotatedElement, Class, Class) * @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class) * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) * @see org.springframework.core.BridgeMethodResolver#findBridgedMethod * @see java.lang.annotation.Repeatable * @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType */ public static Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType, @Nullable Class containerAnnotationType) { try { if (annotatedElement instanceof Method) { annotatedElement = BridgeMethodResolver.findBridgedMethod((Method) annotatedElement); } return new AnnotationCollector<>(annotationType, containerAnnotationType).getResult(annotatedElement); } catch (Throwable ex) { handleIntrospectionFailure(annotatedElement, ex); return Collections.emptySet(); } } /** * Find a single {@link Annotation} of {@code annotationType} on the * supplied {@link AnnotatedElement}. *

Meta-annotations will be searched if the annotation is not * directly present on the supplied element. *

Warning: 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 */ @Nullable public static A findAnnotation( AnnotatedElement annotatedElement, @Nullable Class annotationType) { 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 != null ? synthesizeAnnotation(ann, annotatedElement) : null); } /** * Perform the search algorithm for {@link #findAnnotation(AnnotatedElement, Class)} * avoiding endless recursion by tracking which annotations have already * been visited. * @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 */ @Nullable private static A findAnnotation( AnnotatedElement annotatedElement, Class annotationType, Set visited) { try { A annotation = annotatedElement.getDeclaredAnnotation(annotationType); if (annotation != null) { return annotation; } for (Annotation declaredAnn : getDeclaredAnnotations(annotatedElement)) { Class declaredType = declaredAnn.annotationType(); if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) { annotation = findAnnotation((AnnotatedElement) declaredType, 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 directly present on the given * method itself. *

Correctly handles bridge {@link Method Methods} generated by the compiler. *

Meta-annotations will be searched if the annotation is not * directly present on the method. *

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") @Nullable public static A findAnnotation(Method method, @Nullable Class 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 || clazz == Object.class) { break; } Set annotatedMethods = getAnnotatedMethodsInBaseType(clazz); if (!annotatedMethods.isEmpty()) { for (Method annotatedMethod : annotatedMethods) { if (isOverride(method, annotatedMethod)) { Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod); result = findAnnotation((AnnotatedElement) resolvedSuperMethod, annotationType); if (result != null) { break; } } } } if (result == null) { result = searchOnInterfaces(method, annotationType, clazz.getInterfaces()); } } if (result != null) { result = synthesizeAnnotation(result, method); findAnnotationCache.put(cacheKey, result); } } return result; } @Nullable private static A searchOnInterfaces(Method method, Class annotationType, Class... ifcs) { for (Class ifc : ifcs) { Set annotatedMethods = getAnnotatedMethodsInBaseType(ifc); if (!annotatedMethods.isEmpty()) { for (Method annotatedMethod : annotatedMethods) { if (isOverride(method, annotatedMethod)) { A annotation = getAnnotation(annotatedMethod, annotationType); if (annotation != null) { return annotation; } } } } } return null; } /** * Does the given method override the given candidate method? * @param method the overriding method * @param candidate the potentially overridden method * @since 5.0.8 */ static boolean isOverride(Method method, Method candidate) { if (!candidate.getName().equals(method.getName()) || candidate.getParameterCount() != method.getParameterCount()) { return false; } Class[] paramTypes = method.getParameterTypes(); if (Arrays.equals(candidate.getParameterTypes(), paramTypes)) { return true; } for (int i = 0; i < paramTypes.length; i++) { if (paramTypes[i] != ResolvableType.forMethodParameter(candidate, i, method.getDeclaringClass()).resolve()) { return false; } } return true; } /** * Determine the methods on the given type with searchable annotations on them. * @param baseType the superclass or interface to search * @return the cached set of annotated methods * @since 5.0.5 */ static Set getAnnotatedMethodsInBaseType(Class baseType) { boolean ifcCheck = baseType.isInterface(); if (ifcCheck && ClassUtils.isJavaLanguageInterface(baseType)) { return Collections.emptySet(); } Set annotatedMethods = annotatedBaseTypeCache.get(baseType); if (annotatedMethods != null) { return annotatedMethods; } Method[] methods = (ifcCheck ? baseType.getMethods() : baseType.getDeclaredMethods()); for (Method baseMethod : methods) { try { // Public methods on interfaces (including interface hierarchy), // non-private (and therefore overridable) methods on base classes if ((ifcCheck || !Modifier.isPrivate(baseMethod.getModifiers())) && hasSearchableAnnotations(baseMethod)) { if (annotatedMethods == null) { annotatedMethods = new HashSet<>(); } annotatedMethods.add(baseMethod); } } catch (Throwable ex) { handleIntrospectionFailure(baseMethod, ex); } } if (annotatedMethods == null) { annotatedMethods = Collections.emptySet(); } annotatedBaseTypeCache.put(baseType, annotatedMethods); return annotatedMethods; } /** * Determine whether the specified method has searchable annotations, * i.e. not just {@code java.lang} or {@code org.springframework.lang} * annotations such as {@link Deprecated} and {@link Nullable}. * @param ifcMethod the interface method to check * @@since 5.0.5 */ private static boolean hasSearchableAnnotations(Method ifcMethod) { Annotation[] anns = getDeclaredAnnotations(ifcMethod); if (anns.length == 0) { return false; } for (Annotation ann : anns) { String name = ann.annotationType().getName(); if (!name.startsWith("java.lang.") && !name.startsWith("org.springframework.lang.")) { return true; } } return false; } /** * Retrieve a potentially cached array of declared annotations for the * given element. * @param element the annotated element to introspect * @return a potentially cached array of declared annotations * (only for internal iteration purposes, not for external exposure) * @since 5.1 */ static Annotation[] getDeclaredAnnotations(AnnotatedElement element) { if (element instanceof Class || element instanceof Member) { // Class/Field/Method/Constructor returns a defensively cloned array from getDeclaredAnnotations. // Since we use our result for internal iteration purposes only, it's safe to use a shared copy. return declaredAnnotationsCache.computeIfAbsent(element, AnnotatedElement::getDeclaredAnnotations); } return element.getDeclaredAnnotations(); } /** * Find a single {@link Annotation} of {@code annotationType} on the * supplied {@link Class}, traversing its interfaces, annotations, and * superclasses if the annotation is not directly present on * the given class itself. *

This method explicitly handles class-level annotations which are not * declared as {@link java.lang.annotation.Inherited inherited} as well * as meta-annotations and annotations on interfaces. *

The algorithm operates as follows: *

    *
  1. Search for the annotation on the given class and return it if found. *
  2. Recursively search through all annotations that the given class declares. *
  3. Recursively search through all interfaces that the given class declares. *
  4. Recursively search through the superclass hierarchy of the given class. *
*

Note: in this context, the term recursively 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 */ @Nullable public static A findAnnotation(Class clazz, @Nullable Class 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 * @param synthesize {@code true} if the result should be * {@linkplain #synthesizeAnnotation(Annotation) synthesized} * @return the first matching annotation, or {@code null} if not found * @since 4.2.1 */ @SuppressWarnings("unchecked") @Nullable private static A findAnnotation( Class clazz, @Nullable Class 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) { result = synthesizeAnnotation(result, clazz); 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 visited. * @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 */ @Nullable private static A findAnnotation(Class clazz, Class annotationType, Set visited) { try { A annotation = clazz.getDeclaredAnnotation(annotationType); if (annotation != null) { return annotation; } for (Annotation declaredAnn : getDeclaredAnnotations(clazz)) { Class declaredType = declaredAnn.annotationType(); if (!isInJavaLangAnnotationPackage(declaredType) && visited.add(declaredAnn)) { annotation = findAnnotation(declaredType, 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 || superclass == Object.class) { 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 * directly present. *

If the supplied {@code clazz} is an interface, only the interface * itself will be checked; the inheritance hierarchy for interfaces will * not be traversed. *

Meta-annotations will not be searched. *

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) */ @Nullable public static Class findAnnotationDeclaringClass(Class annotationType, @Nullable Class clazz) { if (clazz == null || clazz == Object.class) { 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 * directly present. *

If the supplied {@code clazz} is an interface, only the interface * itself will be checked; the inheritance hierarchy for interfaces will * not be traversed. *

Meta-annotations will not be searched. *

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 annotation on (may be {@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 * @since 3.2.2 * @see Class#isAnnotationPresent(Class) * @see Class#getDeclaredAnnotations() * @see #findAnnotationDeclaringClass(Class, Class) * @see #isAnnotationDeclaredLocally(Class, Class) */ @Nullable public static Class findAnnotationDeclaringClassForTypes( List> annotationTypes, @Nullable Class clazz) { if (clazz == null || clazz == Object.class) { return null; } for (Class 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. directly present) on the supplied * {@code clazz}. *

The supplied {@link Class} may represent any type. *

Meta-annotations will not be searched. *

Note: This method does not 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 directly present * @see java.lang.Class#getDeclaredAnnotations() * @see java.lang.Class#getDeclaredAnnotation(Class) * @see #isAnnotationInherited(Class, Class) */ public static boolean isAnnotationDeclaredLocally(Class annotationType, Class clazz) { try { return (clazz.getDeclaredAnnotation(annotationType) != null); } catch (Throwable ex) { handleIntrospectionFailure(clazz, ex); return false; } } /** * Determine whether an annotation of the specified {@code annotationType} * is present on the supplied {@code clazz} and is * {@linkplain java.lang.annotation.Inherited inherited} * (i.e. not directly present). *

Meta-annotations will not be searched. *

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 present and inherited * @see Class#isAnnotationPresent(Class) * @see #isAnnotationDeclaredLocally(Class, Class) */ public static boolean isAnnotationInherited(Class annotationType, Class clazz) { return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz)); } /** * Determine if an annotation of type {@code metaAnnotationType} is * meta-present 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 annotationType, @Nullable Class 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 given annotated element is defined in a * {@code java} or in the {@code org.springframework.lang} package. * @param annotatedElement the annotated element to check * @return {@code true} if the given element is in a {@code java} * package or in the {@code org.springframework.lang} package * @since 5.1 */ static boolean hasPlainJavaAnnotationsOnly(@Nullable Object annotatedElement) { Class clazz; if (annotatedElement instanceof Class) { clazz = (Class) annotatedElement; } else if (annotatedElement instanceof Member) { clazz = ((Member) annotatedElement).getDeclaringClass(); } else { return false; } String name = clazz.getName(); return (name.startsWith("java") || name.startsWith("org.springframework.lang.")); } /** * 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(@Nullable Annotation annotation) { return (annotation != null && isInJavaLangAnnotationPackage(annotation.annotationType())); } /** * Determine if the {@link Annotation} with the supplied name is defined * in the core JDK {@code java.lang.annotation} package. * @param annotationType the annotation type to check * @return {@code true} if the annotation is in the {@code java.lang.annotation} package * @since 4.3.8 */ static boolean isInJavaLangAnnotationPackage(@Nullable Class annotationType) { return (annotationType != null && isInJavaLangAnnotationPackage(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 * @return {@code true} if the annotation is in the {@code java.lang.annotation} package * @since 4.2 */ public static boolean isInJavaLangAnnotationPackage(@Nullable String annotationType) { return (annotationType != null && annotationType.startsWith("java.lang.annotation")); } /** * Check the declared attributes of the given annotation, in particular covering * Google App Engine's late arrival of {@code TypeNotPresentExceptionProxy} for * {@code Class} values (instead of early {@code Class.getAnnotations() failure}. *

This method not failing indicates that {@link #getAnnotationAttributes(Annotation)} * won't failure either (when attempted later on). * @param annotation the annotation to validate * @throws IllegalStateException if a declared {@code Class} attribute could not be read * @since 4.3.15 * @see Class#getAnnotations() * @see #getAnnotationAttributes(Annotation) */ public static void validateAnnotation(Annotation annotation) { for (Method method : getAttributeMethods(annotation.annotationType())) { Class returnType = method.getReturnType(); if (returnType == Class.class || returnType == Class[].class) { try { method.invoke(annotation); } catch (Throwable ex) { throw new IllegalStateException("Could not obtain annotation attribute value for " + method, ex); } } } } /** * Retrieve the given annotation's attributes as a {@link Map}, preserving all * attribute types. *

Equivalent to calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)} * with the {@code classValuesAsString} and {@code nestedAnnotationsAsMap} parameters * set to {@code false}. *

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) * @see #getAnnotationAttributes(Annotation, boolean, boolean) * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static Map getAnnotationAttributes(Annotation annotation) { return getAnnotationAttributes(null, annotation); } /** * Retrieve the given annotation's attributes as a {@link Map}. *

Equivalent to calling {@link #getAnnotationAttributes(Annotation, boolean, boolean)} * with the {@code nestedAnnotationsAsMap} parameter set to {@code false}. *

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 * @param classValuesAsString whether to convert Class references into Strings (for * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @return the Map of annotation attributes, with attribute names as keys and * corresponding attribute values as values (never {@code null}) * @see #getAnnotationAttributes(Annotation, boolean, boolean) */ public static Map getAnnotationAttributes( Annotation annotation, boolean classValuesAsString) { return getAnnotationAttributes(annotation, classValuesAsString, false); } /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map. *

This method provides fully recursive annotation reading capabilities on par with * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}. * @param annotation the annotation to retrieve the attributes for * @param classValuesAsString whether to convert Class references into Strings (for * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to convert nested annotations into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @return the annotation attributes (a specialized Map) with attribute names as keys * and corresponding attribute values as values (never {@code null}) * @since 3.1.1 */ public static AnnotationAttributes getAnnotationAttributes( Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap); } /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map. *

Equivalent to calling {@link #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)} * 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 * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static AnnotationAttributes getAnnotationAttributes( @Nullable AnnotatedElement annotatedElement, Annotation annotation) { return getAnnotationAttributes(annotatedElement, annotation, false, false); } /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map. *

This method provides fully recursive annotation reading capabilities on par with * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}. * @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 * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to convert nested annotations into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @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(@Nullable AnnotatedElement annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { return getAnnotationAttributes( (Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap); } private static AnnotationAttributes getAnnotationAttributes(@Nullable Object annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { AnnotationAttributes attributes = retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap); postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap); return attributes; } /** * Retrieve the given annotation's attributes as an {@link AnnotationAttributes} map. *

This method provides fully recursive annotation reading capabilities on par with * the reflection-based {@link org.springframework.core.type.StandardAnnotationMetadata}. *

NOTE: This variant of {@code getAnnotationAttributes()} is * only intended for use within the framework. The following special rules apply: *

    *
  1. Default values will be replaced with default value placeholders.
  2. *
  3. 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.
  4. *
* @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 * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to convert nested annotations into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @return the annotation attributes (a specialized Map) with attribute names as keys * and corresponding attribute values as values (never {@code null}) * @since 4.2 * @see #postProcessAnnotationAttributes */ static AnnotationAttributes retrieveAnnotationAttributes(@Nullable Object annotatedElement, Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { Class 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 && ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) { attributeValue = new DefaultValueHolder(defaultValue); } attributes.put(method.getName(), adaptValue(annotatedElement, attributeValue, classValuesAsString, nestedAnnotationsAsMap)); } 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. *

Nested annotations will be * {@linkplain #synthesizeAnnotation(Annotation, AnnotatedElement) synthesized}. * @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 * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to convert nested annotations into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @return the adapted value, or the original value if no adaptation is needed */ @Nullable static Object adaptValue(@Nullable Object annotatedElement, @Nullable Object value, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { 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; if (nestedAnnotationsAsMap) { return getAnnotationAttributes(annotatedElement, annotation, classValuesAsString, true); } else { return synthesizeAnnotation(annotation, annotatedElement); } } if (value instanceof Annotation[]) { Annotation[] annotations = (Annotation[]) value; if (nestedAnnotationsAsMap) { AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[annotations.length]; for (int i = 0; i < annotations.length; i++) { mappedAnnotations[i] = getAnnotationAttributes(annotatedElement, annotations[i], classValuesAsString, true); } return mappedAnnotations; } else { return synthesizeAnnotationArray(annotations, annotatedElement); } } // Fallback return value; } /** * Register the annotation-declared default values for the given attributes, * if available. * @param attributes the annotation attributes to process * @since 4.3.2 */ public static void registerDefaultValues(AnnotationAttributes attributes) { // Only do defaults scanning for public annotations; we'd run into // IllegalAccessExceptions otherwise, and we don't want to mess with // accessibility in a SecurityManager environment. Class annotationType = attributes.annotationType(); if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) { // Check declared default values of attributes in the annotation type. for (Method annotationAttribute : getAttributeMethods(annotationType)) { String attributeName = annotationAttribute.getName(); Object defaultValue = annotationAttribute.getDefaultValue(); if (defaultValue != null && !attributes.containsKey(attributeName)) { if (defaultValue instanceof Annotation) { defaultValue = getAnnotationAttributes((Annotation) defaultValue, false, true); } else if (defaultValue instanceof Annotation[]) { Annotation[] realAnnotations = (Annotation[]) defaultValue; AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length]; for (int i = 0; i < realAnnotations.length; i++) { mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], false, true); } defaultValue = mappedAnnotations; } attributes.put(attributeName, new DefaultValueHolder(defaultValue)); } } } } /** * Post-process the supplied {@link AnnotationAttributes}, preserving nested * annotations as {@code Annotation} instances. *

Specifically, this method enforces attribute alias semantics * for annotation attributes that are annotated with {@link AliasFor @AliasFor} * 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 * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @since 4.3.2 * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean) * @see #getDefaultValue(Class, String) */ public static void postProcessAnnotationAttributes(@Nullable Object annotatedElement, AnnotationAttributes attributes, boolean classValuesAsString) { postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false); } /** * Post-process the supplied {@link AnnotationAttributes}. *

Specifically, this method enforces attribute alias semantics * for annotation attributes that are annotated with {@link AliasFor @AliasFor} * 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 * compatibility with {@link org.springframework.core.type.AnnotationMetadata}) * or to preserve them as Class references * @param nestedAnnotationsAsMap whether to convert nested annotations into * {@link AnnotationAttributes} maps (for compatibility with * {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as * {@code Annotation} instances * @since 4.2 * @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean) * @see #getDefaultValue(Class, String) */ static void postProcessAnnotationAttributes(@Nullable Object annotatedElement, @Nullable AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { if (attributes == null) { return; } Class annotationType = attributes.annotationType(); // Track which attribute values have already been replaced so that we can short // circuit the search algorithms. Set valuesAlreadyReplaced = new HashSet<>(); if (!attributes.validated) { // Validate @AliasFor configuration Map> aliasMap = getAttributeAliasMap(annotationType); aliasMap.forEach((attributeName, aliasedAttributeNames) -> { if (valuesAlreadyReplaced.contains(attributeName)) { return; } Object value = attributes.get(attributeName); boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder)); for (String aliasedAttributeName : aliasedAttributeNames) { if (valuesAlreadyReplaced.contains(aliasedAttributeName)) { continue; } Object aliasedValue = attributes.get(aliasedAttributeName); boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder)); // Something to validate or replace with an alias? if (valuePresent || aliasPresent) { if (valuePresent && aliasPresent) { // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals(). if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) { String elementAsString = (annotatedElement != null ? annotatedElement.toString() : "unknown element"); throw new AnnotationConfigurationException(String.format( "In AnnotationAttributes for annotation [%s] declared on %s, " + "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " + "but only one is permitted.", attributes.displayName, elementAsString, attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue))); } } else if (aliasPresent) { // Replace value with aliasedValue attributes.put(attributeName, adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(attributeName); } else { // Replace aliasedValue with value attributes.put(aliasedAttributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(aliasedAttributeName); } } } }); attributes.validated = true; } // Replace any remaining placeholders with actual default values for (Map.Entry attributeEntry : attributes.entrySet()) { String attributeName = attributeEntry.getKey(); if (valuesAlreadyReplaced.contains(attributeName)) { continue; } Object value = attributeEntry.getValue(); if (value instanceof DefaultValueHolder) { value = ((DefaultValueHolder) value).defaultValue; attributes.put(attributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); } } } /** * Retrieve the value 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 unless the attribute * value cannot be retrieved due to an {@link AnnotationConfigurationException}, * in which case such an exception will be rethrown * @see #getValue(Annotation, String) */ @Nullable public static Object getValue(Annotation annotation) { return getValue(annotation, VALUE); } /** * Retrieve the value 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 unless the attribute * value cannot be retrieved due to an {@link AnnotationConfigurationException}, * in which case such an exception will be rethrown * @see #getValue(Annotation) * @see #rethrowAnnotationConfigurationException(Throwable) */ @Nullable public static Object getValue(@Nullable Annotation annotation, @Nullable String attributeName) { if (annotation == null || !StringUtils.hasText(attributeName)) { return null; } try { Method method = annotation.annotationType().getDeclaredMethod(attributeName); ReflectionUtils.makeAccessible(method); return method.invoke(annotation); } catch (NoSuchMethodException ex) { return null; } catch (InvocationTargetException ex) { rethrowAnnotationConfigurationException(ex.getTargetException()); throw new IllegalStateException("Could not obtain value for annotation attribute '" + attributeName + "' in " + annotation, ex); } catch (Throwable ex) { handleIntrospectionFailure(annotation.getClass(), ex); return null; } } /** * Retrieve the default value 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) */ @Nullable public static Object getDefaultValue(Annotation annotation) { return getDefaultValue(annotation, VALUE); } /** * Retrieve the default value 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) */ @Nullable public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) { return (annotation != null ? getDefaultValue(annotation.annotationType(), attributeName) : null); } /** * Retrieve the default value of the {@code value} attribute * of a single-element Annotation, given the {@link Class annotation type}. * @param annotationType the annotation type for which the default value should be retrieved * @return the default value, or {@code null} if not found * @see #getDefaultValue(Class, String) */ @Nullable public static Object getDefaultValue(Class annotationType) { return getDefaultValue(annotationType, VALUE); } /** * Retrieve the default value of a named attribute, given the * {@link Class annotation type}. * @param annotationType the annotation type 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) */ @Nullable public static Object getDefaultValue( @Nullable Class annotationType, @Nullable String attributeName) { if (annotationType == null || !StringUtils.hasText(attributeName)) { return null; } try { return annotationType.getDeclaredMethod(attributeName).getDefaultValue(); } catch (Throwable ex) { handleIntrospectionFailure(annotationType, ex); return null; } } /** * Synthesize an annotation from the supplied {@code annotation} * by wrapping it in a dynamic proxy that transparently enforces * attribute alias semantics for annotation attributes that are * annotated with {@link AliasFor @AliasFor}. * @param annotation the annotation to synthesize * @return the synthesized annotation, if the supplied annotation is * synthesizable; {@code null} if the supplied annotation is * {@code null}; otherwise, the supplied annotation unmodified * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #synthesizeAnnotation(Annotation, AnnotatedElement) */ static A synthesizeAnnotation(A annotation) { return synthesizeAnnotation(annotation, null); } /** * Synthesize an annotation from the supplied {@code annotation} * by wrapping it in a dynamic proxy that transparently enforces * attribute alias semantics for annotation attributes that are * annotated with {@link AliasFor @AliasFor}. * @param annotation the annotation to synthesize * @param annotatedElement the element that is annotated with the supplied * annotation; may be {@code null} if unknown * @return the synthesized annotation if the supplied annotation is * synthesizable; {@code null} if the supplied annotation is * {@code null}; otherwise the supplied annotation unmodified * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) * @see #synthesizeAnnotation(Class) */ public static A synthesizeAnnotation( A annotation, @Nullable AnnotatedElement annotatedElement) { return synthesizeAnnotation(annotation, (Object) annotatedElement); } @SuppressWarnings("unchecked") static A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) { if (annotation instanceof SynthesizedAnnotation || hasPlainJavaAnnotationsOnly(annotatedElement)) { return annotation; } Class annotationType = annotation.annotationType(); if (!isSynthesizable(annotationType)) { return annotation; } DefaultAnnotationAttributeExtractor attributeExtractor = new DefaultAnnotationAttributeExtractor(annotation, annotatedElement); InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a // synthesizable annotation before (which needs to declare @AliasFor from the same package) Class[] exposedInterfaces = new Class[] {annotationType, SynthesizedAnnotation.class}; return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler); } /** * Synthesize an annotation from the supplied map of annotation * attributes by wrapping the map in a dynamic proxy that implements an * annotation of the specified {@code annotationType} and transparently * enforces attribute alias semantics for annotation attributes * that are annotated with {@link AliasFor @AliasFor}. *

The supplied map must contain a key-value pair for every attribute * defined in the supplied {@code annotationType} that is not aliased or * does not have a default value. Nested maps and nested arrays of maps * will be recursively synthesized into nested annotations or nested * arrays of annotations, respectively. *

Note that {@link AnnotationAttributes} is a specialized type of * {@link Map} that is an ideal candidate for this method's * {@code attributes} argument. * @param attributes the map of annotation attributes to synthesize * @param annotationType the type of annotation to synthesize * @param annotatedElement the element that is annotated with the annotation * corresponding to the supplied attributes; may be {@code null} if unknown * @return the synthesized annotation * @throws IllegalArgumentException if a required attribute is missing or if an * attribute is not of the correct type * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #synthesizeAnnotation(Annotation, AnnotatedElement) * @see #synthesizeAnnotation(Class) * @see #getAnnotationAttributes(AnnotatedElement, Annotation) * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ @SuppressWarnings("unchecked") public static A synthesizeAnnotation(Map attributes, Class annotationType, @Nullable AnnotatedElement annotatedElement) { MapAnnotationAttributeExtractor attributeExtractor = new MapAnnotationAttributeExtractor(attributes, annotationType, annotatedElement); InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor); Class[] exposedInterfaces = (canExposeSynthesizedMarker(annotationType) ? new Class[] {annotationType, SynthesizedAnnotation.class} : new Class[] {annotationType}); return (A) Proxy.newProxyInstance(annotationType.getClassLoader(), exposedInterfaces, handler); } /** * Synthesize an annotation from its default attributes values. *

This method simply delegates to * {@link #synthesizeAnnotation(Map, Class, AnnotatedElement)}, * supplying an empty map for the source attribute values and {@code null} * for the {@link AnnotatedElement}. * @param annotationType the type of annotation to synthesize * @return the synthesized annotation * @throws IllegalArgumentException if a required attribute is missing * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) * @see #synthesizeAnnotation(Annotation, AnnotatedElement) */ public static A synthesizeAnnotation(Class annotationType) { return synthesizeAnnotation(Collections.emptyMap(), annotationType, null); } /** * Synthesize an array of annotations from the supplied array * of {@code annotations} by creating a new array of the same size and * type and populating it with {@linkplain #synthesizeAnnotation(Annotation) * synthesized} versions of the annotations from the input array. * @param annotations the array of annotations to synthesize * @param annotatedElement the element that is annotated with the supplied * array of annotations; may be {@code null} if unknown * @return a new array of synthesized annotations, or {@code null} if * the supplied array is {@code null} * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #synthesizeAnnotation(Annotation, AnnotatedElement) * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) */ static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, @Nullable Object annotatedElement) { if (hasPlainJavaAnnotationsOnly(annotatedElement)) { return annotations; } Annotation[] synthesized = (Annotation[]) Array.newInstance( annotations.getClass().getComponentType(), annotations.length); for (int i = 0; i < annotations.length; i++) { synthesized[i] = synthesizeAnnotation(annotations[i], annotatedElement); } return synthesized; } /** * Synthesize an array of annotations from the supplied array * of {@code maps} of annotation attributes by creating a new array of * {@code annotationType} with the same size and populating it with * {@linkplain #synthesizeAnnotation(Map, Class, AnnotatedElement) * synthesized} versions of the maps from the input array. * @param maps the array of maps of annotation attributes to synthesize * @param annotationType the type of annotations to synthesize * (never {@code null}) * @return a new array of synthesized annotations, or {@code null} if * the supplied array is {@code null} * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2.1 * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) * @see #synthesizeAnnotationArray(Annotation[], Object) */ @SuppressWarnings("unchecked") @Nullable static A[] synthesizeAnnotationArray( @Nullable Map[] maps, Class annotationType) { if (maps == null) { return null; } A[] synthesized = (A[]) Array.newInstance(annotationType, maps.length); for (int i = 0; i < maps.length; i++) { synthesized[i] = synthesizeAnnotation(maps[i], annotationType, null); } return synthesized; } /** * Get a map of all attribute aliases declared via {@code @AliasFor} * in the supplied annotation type. *

The map is keyed by attribute name with each value representing * a list of names of aliased attributes. *

For explicit alias pairs such as x and y (i.e. where x * is an {@code @AliasFor("y")} and y is an {@code @AliasFor("x")}, there * will be two entries in the map: {@code x -> (y)} and {@code y -> (x)}. *

For implicit aliases (i.e. attributes that are declared * as attribute overrides for the same attribute in the same meta-annotation), * there will be n entries in the map. For example, if x, y, and z are * implicit aliases, the map will contain the following entries: * {@code x -> (y, z)}, {@code y -> (x, z)}, {@code z -> (x, y)}. *

An empty return value implies that the annotation does not declare * any attribute aliases. * @param annotationType the annotation type to find attribute aliases in * @return a map containing attribute aliases (never {@code null}) * @since 4.2 */ static Map> getAttributeAliasMap(@Nullable Class annotationType) { if (annotationType == null) { return Collections.emptyMap(); } Map> map = attributeAliasesCache.get(annotationType); if (map != null) { return map; } map = new LinkedHashMap<>(); for (Method attribute : getAttributeMethods(annotationType)) { List aliasNames = getAttributeAliasNames(attribute); if (!aliasNames.isEmpty()) { map.put(attribute.getName(), aliasNames); } } attributeAliasesCache.put(annotationType, map); return map; } /** * Check whether we can expose our {@link SynthesizedAnnotation} marker for the given annotation type. * @param annotationType the annotation type that we are about to create a synthesized proxy for */ private static boolean canExposeSynthesizedMarker(Class annotationType) { try { return (Class.forName(SynthesizedAnnotation.class.getName(), false, annotationType.getClassLoader()) == SynthesizedAnnotation.class); } catch (ClassNotFoundException ex) { return false; } } /** * Determine if annotations of the supplied {@code annotationType} are * synthesizable (i.e. in need of being wrapped in a dynamic * proxy that provides functionality above that of a standard JDK * annotation). *

Specifically, an annotation is synthesizable if it declares * any attributes that are configured as aliased pairs via * {@link AliasFor @AliasFor} or if any nested annotations used by the * annotation declare such aliased pairs. * @since 4.2 * @see SynthesizedAnnotation * @see SynthesizedAnnotationInvocationHandler */ @SuppressWarnings("unchecked") private static boolean isSynthesizable(Class annotationType) { if (hasPlainJavaAnnotationsOnly(annotationType)) { return false; } Boolean synthesizable = synthesizableCache.get(annotationType); if (synthesizable != null) { return synthesizable; } synthesizable = Boolean.FALSE; for (Method attribute : getAttributeMethods(annotationType)) { if (!getAttributeAliasNames(attribute).isEmpty()) { synthesizable = Boolean.TRUE; break; } Class returnType = attribute.getReturnType(); if (Annotation[].class.isAssignableFrom(returnType)) { Class nestedAnnotationType = (Class) returnType.getComponentType(); if (isSynthesizable(nestedAnnotationType)) { synthesizable = Boolean.TRUE; break; } } else if (Annotation.class.isAssignableFrom(returnType)) { Class nestedAnnotationType = (Class) returnType; if (isSynthesizable(nestedAnnotationType)) { synthesizable = Boolean.TRUE; break; } } } synthesizableCache.put(annotationType, synthesizable); return synthesizable; } /** * Get the names of the aliased attributes configured via * {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}. * @param attribute the attribute to find aliases for * @return the names of the aliased attributes (never {@code null}, though * potentially empty) * @throws IllegalArgumentException if the supplied attribute method is * {@code null} or not from an annotation * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 * @see #getAttributeOverrideName(Method, Class) */ static List getAttributeAliasNames(Method attribute) { AliasDescriptor descriptor = AliasDescriptor.from(attribute); return (descriptor != null ? descriptor.getAttributeAliasNames() : Collections.emptyList()); } /** * Get the name of the overridden attribute configured via * {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}. * @param attribute the attribute from which to retrieve the override * (never {@code null}) * @param metaAnnotationType the type of meta-annotation in which the * overridden attribute is allowed to be declared * @return the name of the overridden attribute, or {@code null} if not * found or not applicable for the specified meta-annotation type * @throws IllegalArgumentException if the supplied attribute method is * {@code null} or not from an annotation, or if the supplied meta-annotation * type is {@code null} or {@link Annotation} * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected * @since 4.2 */ @Nullable static String getAttributeOverrideName(Method attribute, @Nullable Class metaAnnotationType) { AliasDescriptor descriptor = AliasDescriptor.from(attribute); return (descriptor != null && metaAnnotationType != null ? descriptor.getAttributeOverrideName(metaAnnotationType) : null); } /** * Get all methods declared in the supplied {@code annotationType} that * match Java's requirements for annotation attributes. *

All methods in the returned list will be * {@linkplain ReflectionUtils#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 empty) * @since 4.2 */ static List getAttributeMethods(Class annotationType) { List methods = attributeMethodsCache.get(annotationType); if (methods != null) { return methods; } methods = new ArrayList<>(); for (Method method : annotationType.getDeclaredMethods()) { if (isAttributeMethod(method)) { ReflectionUtils.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 */ @Nullable 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(@Nullable 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 * @since 4.2 * @see Annotation#annotationType() */ static boolean isAnnotationTypeMethod(@Nullable Method method) { return (method != null && method.getName().equals("annotationType") && method.getParameterCount() == 0); } /** * Resolve the container type for the supplied repeatable {@code annotationType}. *

Automatically detects a container annotation declared via * {@link java.lang.annotation.Repeatable}. If the supplied annotation type * is not annotated with {@code @Repeatable}, this method simply returns * {@code null}. * @since 4.2 */ @Nullable static Class resolveContainerAnnotationType(Class annotationType) { Repeatable repeatable = getAnnotation(annotationType, Repeatable.class); return (repeatable != null ? repeatable.value() : null); } /** * If the supplied throwable is an {@link AnnotationConfigurationException}, * it will be cast to an {@code AnnotationConfigurationException} and thrown, * allowing it to propagate to the caller. *

Otherwise, this method does nothing. * @param ex the throwable to inspect * @since 4.2 */ static void rethrowAnnotationConfigurationException(Throwable ex) { if (ex instanceof AnnotationConfigurationException) { throw (AnnotationConfigurationException) ex; } } /** * Handle the supplied annotation introspection exception. *

If the supplied exception is an {@link AnnotationConfigurationException}, * it will simply be thrown, allowing it to propagate to the caller, and * nothing will be logged. *

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(@Nullable AnnotatedElement element, Throwable ex) { rethrowAnnotationConfigurationException(ex); Log loggerToUse = logger; if (loggerToUse == null) { loggerToUse = LogFactory.getLog(AnnotationUtils.class); logger = loggerToUse; } if (element instanceof Class && Annotation.class.isAssignableFrom((Class) element)) { // Meta-annotation or (default) value lookup on an annotation type if (loggerToUse.isDebugEnabled()) { loggerToUse.debug("Failed to meta-introspect annotation " + element + ": " + ex); } } else { // Direct annotation lookup on regular Class, Method, Field if (loggerToUse.isInfoEnabled()) { loggerToUse.info("Failed to introspect annotations on " + element + ": " + ex); } } } /** * Clear the internal annotation metadata cache. * @since 4.3.15 */ public static void clearCache() { findAnnotationCache.clear(); metaPresentCache.clear(); declaredAnnotationsCache.clear(); annotatedBaseTypeCache.clear(); synthesizableCache.clear(); attributeAliasesCache.clear(); attributeMethodsCache.clear(); aliasDescriptorCache.clear(); } /** * Cache key for the AnnotatedElement cache. */ private static final class AnnotationCacheKey implements Comparable { private final AnnotatedElement element; private final Class annotationType; public AnnotationCacheKey(AnnotatedElement element, Class 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 { private final Class annotationType; @Nullable private final Class containerAnnotationType; private final Set visited = new HashSet<>(); private final Set result = new LinkedHashSet<>(); AnnotationCollector(Class annotationType, @Nullable Class containerAnnotationType) { this.annotationType = annotationType; this.containerAnnotationType = (containerAnnotationType != null ? containerAnnotationType : resolveContainerAnnotationType(annotationType)); } Set 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 = getDeclaredAnnotations(element); for (Annotation ann : annotations) { Class currentAnnotationType = ann.annotationType(); if (ObjectUtils.nullSafeEquals(this.annotationType, currentAnnotationType)) { this.result.add(synthesizeAnnotation((A) ann, element)); } else if (ObjectUtils.nullSafeEquals(this.containerAnnotationType, currentAnnotationType)) { this.result.addAll(getValue(element, ann)); } else if (!isInJavaLangAnnotationPackage(currentAnnotationType)) { process(currentAnnotationType); } } } catch (Throwable ex) { handleIntrospectionFailure(element, ex); } } } @SuppressWarnings("unchecked") private List getValue(AnnotatedElement element, Annotation annotation) { try { List synthesizedAnnotations = new ArrayList<>(); A[] value = (A[]) AnnotationUtils.getValue(annotation); if (value != null) { for (A anno : value) { synthesizedAnnotations.add(synthesizeAnnotation(anno, element)); } } return synthesizedAnnotations; } catch (Throwable ex) { handleIntrospectionFailure(element, ex); } // Unable to read value from repeating annotation container -> ignore it. return Collections.emptyList(); } } /** * {@code AliasDescriptor} encapsulates the declaration of {@code @AliasFor} * on a given annotation attribute and includes support for validating * the configuration of aliases (both explicit and implicit). * @since 4.2.1 * @see #from * @see #getAttributeAliasNames * @see #getAttributeOverrideName */ private static final class AliasDescriptor { private final Method sourceAttribute; private final Class sourceAnnotationType; private final String sourceAttributeName; private final Method aliasedAttribute; private final Class aliasedAnnotationType; private final String aliasedAttributeName; private final boolean isAliasPair; /** * Create an {@code AliasDescriptor} from the declaration * of {@code @AliasFor} on the supplied annotation attribute and * validate the configuration of {@code @AliasFor}. * @param attribute the annotation attribute that is annotated with * {@code @AliasFor} * @return an alias descriptor, or {@code null} if the attribute * is not annotated with {@code @AliasFor} * @see #validateAgainst */ @Nullable public static AliasDescriptor from(Method attribute) { AliasDescriptor descriptor = aliasDescriptorCache.get(attribute); if (descriptor != null) { return descriptor; } AliasFor aliasFor = attribute.getAnnotation(AliasFor.class); if (aliasFor == null) { return null; } descriptor = new AliasDescriptor(attribute, aliasFor); descriptor.validate(); aliasDescriptorCache.put(attribute, descriptor); return descriptor; } @SuppressWarnings("unchecked") private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) { Class declaringClass = sourceAttribute.getDeclaringClass(); this.sourceAttribute = sourceAttribute; this.sourceAnnotationType = (Class) declaringClass; this.sourceAttributeName = sourceAttribute.getName(); this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ? this.sourceAnnotationType : aliasFor.annotation()); this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute); if (this.aliasedAnnotationType == this.sourceAnnotationType && this.aliasedAttributeName.equals(this.sourceAttributeName)) { String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " + "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.", sourceAttribute.getName(), declaringClass.getName()); throw new AnnotationConfigurationException(msg); } try { this.aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName); } catch (NoSuchMethodException ex) { String msg = String.format( "Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg, ex); } this.isAliasPair = (this.sourceAnnotationType == this.aliasedAnnotationType); } private void validate() { // Target annotation is not meta-present? if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) { String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " + "an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg); } if (this.isAliasPair) { AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class); if (mirrorAliasFor == null) { String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].", this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName); throw new AnnotationConfigurationException(msg); } String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute); if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) { String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].", this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName, mirrorAliasedAttributeName); throw new AnnotationConfigurationException(msg); } } Class returnType = this.sourceAttribute.getReturnType(); Class aliasedReturnType = this.aliasedAttribute.getReturnType(); if (returnType != aliasedReturnType && (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) { String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + "and attribute '%s' in annotation [%s] must declare the same return type.", this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName, this.aliasedAnnotationType.getName()); throw new AnnotationConfigurationException(msg); } if (this.isAliasPair) { validateDefaultValueConfiguration(this.aliasedAttribute); } } private void validateDefaultValueConfiguration(Method aliasedAttribute) { Object defaultValue = this.sourceAttribute.getDefaultValue(); Object aliasedDefaultValue = aliasedAttribute.getDefaultValue(); if (defaultValue == null || aliasedDefaultValue == null) { String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + "and attribute '%s' in annotation [%s] must declare default values.", this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(), aliasedAttribute.getDeclaringClass().getName()); throw new AnnotationConfigurationException(msg); } if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) { String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " + "and attribute '%s' in annotation [%s] must declare the same default value.", this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(), aliasedAttribute.getDeclaringClass().getName()); throw new AnnotationConfigurationException(msg); } } /** * Validate this descriptor against the supplied descriptor. *

This method only validates the configuration of default values * for the two descriptors, since other aspects of the descriptors * are validated when they are created. */ private void validateAgainst(AliasDescriptor otherDescriptor) { validateDefaultValueConfiguration(otherDescriptor.sourceAttribute); } /** * Determine if this descriptor represents an explicit override for * an attribute in the supplied {@code metaAnnotationType}. * @see #isAliasFor */ private boolean isOverrideFor(Class metaAnnotationType) { return (this.aliasedAnnotationType == metaAnnotationType); } /** * Determine if this descriptor and the supplied descriptor both * effectively represent aliases for the same attribute in the same * target annotation, either explicitly or implicitly. *

This method searches the attribute override hierarchy, beginning * with this descriptor, in order to detect implicit and transitively * implicit aliases. * @return {@code true} if this descriptor and the supplied descriptor * effectively alias the same annotation attribute * @see #isOverrideFor */ private boolean isAliasFor(AliasDescriptor otherDescriptor) { for (AliasDescriptor lhs = this; lhs != null; lhs = lhs.getAttributeOverrideDescriptor()) { for (AliasDescriptor rhs = otherDescriptor; rhs != null; rhs = rhs.getAttributeOverrideDescriptor()) { if (lhs.aliasedAttribute.equals(rhs.aliasedAttribute)) { return true; } } } return false; } public List getAttributeAliasNames() { // Explicit alias pair? if (this.isAliasPair) { return Collections.singletonList(this.aliasedAttributeName); } // Else: search for implicit aliases List aliases = new ArrayList<>(); for (AliasDescriptor otherDescriptor : getOtherDescriptors()) { if (this.isAliasFor(otherDescriptor)) { this.validateAgainst(otherDescriptor); aliases.add(otherDescriptor.sourceAttributeName); } } return aliases; } private List getOtherDescriptors() { List otherDescriptors = new ArrayList<>(); for (Method currentAttribute : getAttributeMethods(this.sourceAnnotationType)) { if (!this.sourceAttribute.equals(currentAttribute)) { AliasDescriptor otherDescriptor = AliasDescriptor.from(currentAttribute); if (otherDescriptor != null) { otherDescriptors.add(otherDescriptor); } } } return otherDescriptors; } @Nullable public String getAttributeOverrideName(Class metaAnnotationType) { // Search the attribute override hierarchy, starting with the current attribute for (AliasDescriptor desc = this; desc != null; desc = desc.getAttributeOverrideDescriptor()) { if (desc.isOverrideFor(metaAnnotationType)) { return desc.aliasedAttributeName; } } // Else: explicit attribute override for a different meta-annotation return null; } @Nullable private AliasDescriptor getAttributeOverrideDescriptor() { if (this.isAliasPair) { return null; } return AliasDescriptor.from(this.aliasedAttribute); } /** * Get the name of the aliased attribute configured via the supplied * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}, * or the original attribute if no aliased one specified (indicating that * the reference goes to a same-named attribute on a meta-annotation). *

This method returns the value of either the {@code attribute} * or {@code value} attribute of {@code @AliasFor}, ensuring that only * one of the attributes has been declared while simultaneously ensuring * that at least one of the attributes has been declared. * @param aliasFor the {@code @AliasFor} annotation from which to retrieve * the aliased attribute name * @param attribute the attribute that is annotated with {@code @AliasFor} * @return the name of the aliased attribute (never {@code null} or empty) * @throws AnnotationConfigurationException if invalid configuration of * {@code @AliasFor} is detected */ private String getAliasedAttributeName(AliasFor aliasFor, Method attribute) { String attributeName = aliasFor.attribute(); String value = aliasFor.value(); boolean attributeDeclared = StringUtils.hasText(attributeName); boolean valueDeclared = StringUtils.hasText(value); // Ensure user did not declare both 'value' and 'attribute' in @AliasFor if (attributeDeclared && valueDeclared) { String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " + "and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.", attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value); throw new AnnotationConfigurationException(msg); } // Either explicit attribute name or pointing to same-named attribute by default attributeName = (attributeDeclared ? attributeName : value); return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName()); } @Override public String toString() { return String.format("%s: @%s(%s) is an alias for @%s(%s)", getClass().getSimpleName(), this.sourceAnnotationType.getSimpleName(), this.sourceAttributeName, this.aliasedAnnotationType.getSimpleName(), this.aliasedAttributeName); } } private static class DefaultValueHolder { final Object defaultValue; public DefaultValueHolder(Object defaultValue) { this.defaultValue = defaultValue; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy