diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java index 03551e35ee..bf4a08dc34 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractAliasAwareAnnotationAttributeExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -64,7 +64,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor implements Anno this.annotationType = annotationType; this.annotatedElement = annotatedElement; this.source = source; - this.attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType); + this.attributeAliasMap = InternalAnnotationUtils.getAttributeAliasMap(annotationType); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 6e1552aa1c..fbd308c9cb 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -18,21 +18,11 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Set; import org.springframework.core.BridgeMethodResolver; import org.springframework.lang.Nullable; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; /** @@ -97,17 +87,6 @@ import org.springframework.util.MultiValueMap; */ public abstract class AnnotatedElementUtils { - /** - * {@code null} constant used to denote that the search algorithm should continue. - */ - @Nullable - private static final Boolean CONTINUE = null; - - private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; - - private static final Processor alwaysTrueAnnotationProcessor = new AlwaysTrueBooleanAnnotationProcessor(); - - /** * Build an adapted {@link AnnotatedElement} for the given annotations, * typically for use with other methods on {@link AnnotatedElementUtils}. @@ -115,27 +94,7 @@ public abstract class AnnotatedElementUtils { * @since 4.3 */ public static AnnotatedElement forAnnotations(final Annotation... annotations) { - return new AnnotatedElement() { - @Override - @SuppressWarnings("unchecked") - @Nullable - public T getAnnotation(Class annotationClass) { - for (Annotation ann : annotations) { - if (ann.annotationType() == annotationClass) { - return (T) ann; - } - } - return null; - } - @Override - public Annotation[] getAnnotations() { - return annotations; - } - @Override - public Annotation[] getDeclaredAnnotations() { - return annotations; - } - }; + return InternalAnnotatedElementUtils.forAnnotations(annotations); } /** @@ -153,7 +112,7 @@ public abstract class AnnotatedElementUtils { * @see #hasMetaAnnotationTypes */ public static Set getMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { - return getMetaAnnotationTypes(element, element.getAnnotation(annotationType)); + return InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationType); } /** @@ -171,30 +130,7 @@ public abstract class AnnotatedElementUtils { * @see #hasMetaAnnotationTypes */ public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationName) { - return getMetaAnnotationTypes(element, AnnotationUtils.getAnnotation(element, annotationName)); - } - - private static Set getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation composed) { - if (composed == null) { - return Collections.emptySet(); - } - - try { - final Set types = new LinkedHashSet<>(); - searchWithGetSemantics(composed.annotationType(), Collections.emptySet(), null, null, new SimpleAnnotationProcessor(true) { - @Override - @Nullable - public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { - types.add(annotation.annotationType().getName()); - return CONTINUE; - } - }, new HashSet<>(), 1); - return types; - } - catch (Throwable ex) { - AnnotationUtils.rethrowAnnotationConfigurationException(ex); - throw new IllegalStateException("Failed to introspect annotations on " + element, ex); - } + return InternalAnnotatedElementUtils.getMetaAnnotationTypes(element, annotationName); } /** @@ -210,7 +146,7 @@ public abstract class AnnotatedElementUtils { * @see #getMetaAnnotationTypes */ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { - return hasMetaAnnotationTypes(element, annotationType, null); + return InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationType); } /** @@ -226,20 +162,7 @@ public abstract class AnnotatedElementUtils { * @see #getMetaAnnotationTypes */ public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) { - return hasMetaAnnotationTypes(element, null, annotationName); - } - - private static boolean hasMetaAnnotationTypes( - AnnotatedElement element, @Nullable Class annotationType, @Nullable String annotationName) { - - return Boolean.TRUE.equals( - searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor() { - @Override - @Nullable - public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { - return (metaDepth > 0 ? Boolean.TRUE : CONTINUE); - } - })); + return InternalAnnotatedElementUtils.hasMetaAnnotationTypes(element, annotationName); } /** @@ -257,11 +180,7 @@ public abstract class AnnotatedElementUtils { * @see #hasAnnotation(AnnotatedElement, Class) */ public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { - // Shortcut: directly present on the element, with no processing needed? - if (element.isAnnotationPresent(annotationType)) { - return true; - } - return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor)); + return InternalAnnotatedElementUtils.isAnnotated(element, annotationType); } /** @@ -277,7 +196,7 @@ public abstract class AnnotatedElementUtils { * @return {@code true} if a matching annotation is present */ public static boolean isAnnotated(AnnotatedElement element, String annotationName) { - return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor)); + return InternalAnnotatedElementUtils.isAnnotated(element, annotationName); } /** @@ -300,11 +219,8 @@ public abstract class AnnotatedElementUtils { @Nullable public static AnnotationAttributes getMergedAnnotationAttributes( AnnotatedElement element, Class annotationType) { - - AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType, null, - new MergedAnnotationAttributesProcessor()); - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); - return attributes; + return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, + annotationType); } /** @@ -327,7 +243,8 @@ public abstract class AnnotatedElementUtils { */ @Nullable public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, String annotationName) { - return getMergedAnnotationAttributes(element, annotationName, false, false); + return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, + annotationName); } /** @@ -359,11 +276,8 @@ public abstract class AnnotatedElementUtils { @Nullable public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - - AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName, - new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); - return attributes; + return InternalAnnotatedElementUtils.getMergedAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap); } /** @@ -386,20 +300,7 @@ public abstract class AnnotatedElementUtils { */ @Nullable public static A getMergedAnnotation(AnnotatedElement element, Class annotationType) { - // Shortcut: directly present on the element, with no merging needed? - A annotation = element.getDeclaredAnnotation(annotationType); - if (annotation != null) { - return AnnotationUtils.synthesizeAnnotation(annotation, element); - } - - // Shortcut: no searchable annotations to be found on plain Java classes and org.springframework.lang types... - if (AnnotationUtils.hasPlainJavaAnnotationsOnly(element)) { - return null; - } - - // Exhaustive retrieval of merged annotation attributes... - AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType); - return (attributes != null ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); + return InternalAnnotatedElementUtils.getMergedAnnotation(element, annotationType); } /** @@ -423,9 +324,8 @@ public abstract class AnnotatedElementUtils { * @see #findAllMergedAnnotations(AnnotatedElement, Class) */ public static Set getAllMergedAnnotations(AnnotatedElement element, Class annotationType) { - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithGetSemantics(element, annotationType, null, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + return InternalAnnotatedElementUtils.getAllMergedAnnotations(element, + annotationType); } /** @@ -447,9 +347,8 @@ public abstract class AnnotatedElementUtils { * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ public static Set getAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithGetSemantics(element, annotationTypes, null, null, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + return InternalAnnotatedElementUtils.getAllMergedAnnotations(element, + annotationTypes); } /** @@ -478,8 +377,8 @@ public abstract class AnnotatedElementUtils { */ public static Set getMergedRepeatableAnnotations(AnnotatedElement element, Class annotationType) { - - return getMergedRepeatableAnnotations(element, annotationType, null); + return InternalAnnotatedElementUtils.getMergedRepeatableAnnotations(element, + annotationType); } /** @@ -510,17 +409,8 @@ public abstract class AnnotatedElementUtils { */ public static Set getMergedRepeatableAnnotations(AnnotatedElement element, Class annotationType, @Nullable Class containerType) { - - if (containerType == null) { - containerType = resolveContainerType(annotationType); - } - else { - validateContainerType(annotationType, containerType); - } - - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithGetSemantics(element, Collections.singleton(annotationType), null, containerType, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + return InternalAnnotatedElementUtils.getMergedRepeatableAnnotations(element, + annotationType, containerType); } /** @@ -539,7 +429,8 @@ public abstract class AnnotatedElementUtils { */ @Nullable public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationName) { - return getAllAnnotationAttributes(element, annotationName, false, false); + return InternalAnnotatedElementUtils.getAllAnnotationAttributes(element, + annotationName); } /** @@ -563,21 +454,8 @@ public abstract class AnnotatedElementUtils { @Nullable public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { - - final MultiValueMap attributesMap = new LinkedMultiValueMap<>(); - - searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor() { - @Override - @Nullable - public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { - AnnotationAttributes annotationAttributes = AnnotationUtils.getAnnotationAttributes( - annotation, classValuesAsString, nestedAnnotationsAsMap); - annotationAttributes.forEach(attributesMap::add); - return CONTINUE; - } - }); - - return (!attributesMap.isEmpty() ? attributesMap : null); + return InternalAnnotatedElementUtils.getAllAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap); } /** @@ -595,11 +473,7 @@ public abstract class AnnotatedElementUtils { * @see #isAnnotated(AnnotatedElement, Class) */ public static boolean hasAnnotation(AnnotatedElement element, Class annotationType) { - // Shortcut: directly present on the element, with no processing needed? - if (element.isAnnotationPresent(annotationType)) { - return true; - } - return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor)); + return InternalAnnotatedElementUtils.hasAnnotation(element, annotationType); } /** @@ -632,11 +506,8 @@ public abstract class AnnotatedElementUtils { @Nullable public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, Class annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - - AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, null, - new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); - return attributes; + return InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, + annotationType, classValuesAsString, nestedAnnotationsAsMap); } /** @@ -669,11 +540,8 @@ public abstract class AnnotatedElementUtils { @Nullable public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - - AnnotationAttributes attributes = searchWithFindSemantics(element, null, annotationName, - new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); - return attributes; + return InternalAnnotatedElementUtils.findMergedAnnotationAttributes(element, + annotationName, classValuesAsString, nestedAnnotationsAsMap); } /** @@ -696,20 +564,8 @@ public abstract class AnnotatedElementUtils { */ @Nullable public static A findMergedAnnotation(AnnotatedElement element, Class annotationType) { - // Shortcut: directly present on the element, with no merging needed? - A annotation = element.getDeclaredAnnotation(annotationType); - if (annotation != null) { - return AnnotationUtils.synthesizeAnnotation(annotation, element); - } - - // Shortcut: no searchable annotations to be found on plain Java classes and org.springframework.lang types... - if (AnnotationUtils.hasPlainJavaAnnotationsOnly(element)) { - return null; - } - - // Exhaustive retrieval of merged annotation attributes... - AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false); - return (attributes != null ? AnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); + return InternalAnnotatedElementUtils.findMergedAnnotation(element, + annotationType); } /** @@ -732,9 +588,8 @@ public abstract class AnnotatedElementUtils { * @see #getAllMergedAnnotations(AnnotatedElement, Class) */ public static Set findAllMergedAnnotations(AnnotatedElement element, Class annotationType) { - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithFindSemantics(element, annotationType, null, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + return InternalAnnotatedElementUtils.findAllMergedAnnotations(element, + annotationType); } /** @@ -756,9 +611,8 @@ public abstract class AnnotatedElementUtils { * @see #findAllMergedAnnotations(AnnotatedElement, Class) */ public static Set findAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithFindSemantics(element, annotationTypes, null, null, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + return InternalAnnotatedElementUtils.findAllMergedAnnotations(element, + annotationTypes); } /** @@ -787,8 +641,8 @@ public abstract class AnnotatedElementUtils { */ public static Set findMergedRepeatableAnnotations(AnnotatedElement element, Class annotationType) { - - return findMergedRepeatableAnnotations(element, annotationType, null); + return InternalAnnotatedElementUtils.findMergedRepeatableAnnotations(element, + annotationType); } /** @@ -819,826 +673,8 @@ public abstract class AnnotatedElementUtils { */ public static Set findMergedRepeatableAnnotations(AnnotatedElement element, Class annotationType, @Nullable Class containerType) { - - if (containerType == null) { - containerType = resolveContainerType(annotationType); - } - else { - validateContainerType(annotationType, containerType); - } - - MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); - searchWithFindSemantics(element, Collections.singleton(annotationType), null, containerType, processor); - return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); - } - - /** - * Search for annotations of the specified {@code annotationName} or - * {@code annotationType} on the specified {@code element}, following - * get semantics. - * @param element the annotated element - * @param annotationType the annotation type to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param processor the processor to delegate to - * @return the result of the processor (potentially {@code null}) - */ - @Nullable - private static T searchWithGetSemantics(AnnotatedElement element, - @Nullable Class annotationType, - @Nullable String annotationName, Processor processor) { - - return searchWithGetSemantics(element, - (annotationType != null ? Collections.singleton(annotationType) : Collections.emptySet()), - annotationName, null, processor); - } - - /** - * Search for annotations of the specified {@code annotationName} or - * {@code annotationType} on the specified {@code element}, following - * get semantics. - * @param element the annotated element - * @param annotationTypes the annotation types to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param containerType the type of the container that holds repeatable - * annotations, or {@code null} if the annotation is not repeatable - * @param processor the processor to delegate to - * @return the result of the processor (potentially {@code null}) - * @since 4.3 - */ - @Nullable - private static T searchWithGetSemantics(AnnotatedElement element, - Set> annotationTypes, @Nullable String annotationName, - @Nullable Class containerType, Processor processor) { - - try { - return searchWithGetSemantics(element, annotationTypes, annotationName, containerType, processor, - new HashSet<>(), 0); - } - catch (Throwable ex) { - AnnotationUtils.rethrowAnnotationConfigurationException(ex); - throw new IllegalStateException("Failed to introspect annotations on " + element, ex); - } - } - - /** - * Perform the search algorithm for the {@link #searchWithGetSemantics} - * method, avoiding endless recursion by tracking which annotated elements - * have already been visited. - *

The {@code metaDepth} parameter is explained in the - * {@link Processor#process process()} method of the {@link Processor} API. - * @param element the annotated element - * @param annotationTypes the annotation types to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param containerType the type of the container that holds repeatable - * annotations, or {@code null} if the annotation is not repeatable - * @param processor the processor to delegate to - * @param visited the set of annotated elements that have already been visited - * @param metaDepth the meta-depth of the annotation - * @return the result of the processor (potentially {@code null}) - */ - @Nullable - private static T searchWithGetSemantics(AnnotatedElement element, - Set> annotationTypes, @Nullable String annotationName, - @Nullable Class containerType, Processor processor, - Set visited, int metaDepth) { - - if (visited.add(element)) { - try { - // Start searching within locally declared annotations - List declaredAnnotations = Arrays.asList(AnnotationUtils.getDeclaredAnnotations(element)); - T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations, - annotationTypes, annotationName, containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - - if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new - Class superclass = ((Class) element).getSuperclass(); - if (superclass != null && superclass != Object.class) { - List inheritedAnnotations = new LinkedList<>(); - for (Annotation annotation : element.getAnnotations()) { - if (!declaredAnnotations.contains(annotation)) { - inheritedAnnotations.add(annotation); - } - } - // Continue searching within inherited annotations - result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations, - annotationTypes, annotationName, containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - } - } - catch (Throwable ex) { - AnnotationUtils.handleIntrospectionFailure(element, ex); - } - } - - return null; - } - - /** - * This method is invoked by {@link #searchWithGetSemantics} to perform - * the actual search within the supplied list of annotations. - *

This method should be invoked first with locally declared annotations - * and then subsequently with inherited annotations, thereby allowing - * local annotations to take precedence over inherited annotations. - *

The {@code metaDepth} parameter is explained in the - * {@link Processor#process process()} method of the {@link Processor} API. - * @param element the element that is annotated with the supplied - * annotations, used for contextual logging; may be {@code null} if unknown - * @param annotations the annotations to search in - * @param annotationTypes the annotation types to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param containerType the type of the container that holds repeatable - * annotations, or {@code null} if the annotation is not repeatable - * @param processor the processor to delegate to - * @param visited the set of annotated elements that have already been visited - * @param metaDepth the meta-depth of the annotation - * @return the result of the processor (potentially {@code null}) - * @since 4.2 - */ - @Nullable - private static T searchWithGetSemanticsInAnnotations(@Nullable AnnotatedElement element, - List annotations, Set> annotationTypes, - @Nullable String annotationName, @Nullable Class containerType, - Processor processor, Set visited, int metaDepth) { - - // Search in annotations - for (Annotation annotation : annotations) { - Class currentAnnotationType = annotation.annotationType(); - if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { - if (annotationTypes.contains(currentAnnotationType) || - currentAnnotationType.getName().equals(annotationName) || - processor.alwaysProcesses()) { - T result = processor.process(element, annotation, metaDepth); - if (result != null) { - if (processor.aggregates() && metaDepth == 0) { - processor.getAggregatedResults().add(result); - } - else { - return result; - } - } - } - // Repeatable annotations in container? - else if (currentAnnotationType == containerType) { - for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) { - T result = processor.process(element, contained, metaDepth); - if (result != null) { - // No need to post-process since repeatable annotations within a - // container cannot be composed annotations. - processor.getAggregatedResults().add(result); - } - } - } - } - } - - // Recursively search in meta-annotations - for (Annotation annotation : annotations) { - Class currentAnnotationType = annotation.annotationType(); - if (!AnnotationUtils.hasPlainJavaAnnotationsOnly(currentAnnotationType)) { - T result = searchWithGetSemantics(currentAnnotationType, annotationTypes, - annotationName, containerType, processor, visited, metaDepth + 1); - if (result != null) { - processor.postProcess(element, annotation, result); - if (processor.aggregates() && metaDepth == 0) { - processor.getAggregatedResults().add(result); - } - else { - return result; - } - } - } - } - - return null; - } - - /** - * Search for annotations of the specified {@code annotationName} or - * {@code annotationType} on the specified {@code element}, following - * find semantics. - * @param element the annotated element - * @param annotationType the annotation type to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param processor the processor to delegate to - * @return the result of the processor (potentially {@code null}) - * @since 4.2 - */ - @Nullable - private static T searchWithFindSemantics(AnnotatedElement element, - @Nullable Class annotationType, - @Nullable String annotationName, Processor processor) { - - return searchWithFindSemantics(element, - (annotationType != null ? Collections.singleton(annotationType) : Collections.emptySet()), - annotationName, null, processor); - } - - /** - * Search for annotations of the specified {@code annotationName} or - * {@code annotationType} on the specified {@code element}, following - * find semantics. - * @param element the annotated element - * @param annotationTypes the annotation types to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param containerType the type of the container that holds repeatable - * annotations, or {@code null} if the annotation is not repeatable - * @param processor the processor to delegate to - * @return the result of the processor (potentially {@code null}) - * @since 4.3 - */ - @Nullable - private static T searchWithFindSemantics(AnnotatedElement element, - Set> annotationTypes, @Nullable String annotationName, - @Nullable Class containerType, Processor processor) { - - if (containerType != null && !processor.aggregates()) { - throw new IllegalArgumentException( - "Searches for repeatable annotations must supply an aggregating Processor"); - } - - try { - return searchWithFindSemantics( - element, annotationTypes, annotationName, containerType, processor, new HashSet<>(), 0); - } - catch (Throwable ex) { - AnnotationUtils.rethrowAnnotationConfigurationException(ex); - throw new IllegalStateException("Failed to introspect annotations on " + element, ex); - } - } - - /** - * Perform the search algorithm for the {@link #searchWithFindSemantics} - * method, avoiding endless recursion by tracking which annotated elements - * have already been visited. - *

The {@code metaDepth} parameter is explained in the - * {@link Processor#process process()} method of the {@link Processor} API. - * @param element the annotated element (never {@code null}) - * @param annotationTypes the annotation types to find - * @param annotationName the fully qualified class name of the annotation - * type to find (as an alternative to {@code annotationType}) - * @param containerType the type of the container that holds repeatable - * annotations, or {@code null} if the annotation is not repeatable - * @param processor the processor to delegate to - * @param visited the set of annotated elements that have already been visited - * @param metaDepth the meta-depth of the annotation - * @return the result of the processor (potentially {@code null}) - * @since 4.2 - */ - @Nullable - private static T searchWithFindSemantics(AnnotatedElement element, - Set> annotationTypes, @Nullable String annotationName, - @Nullable Class containerType, Processor processor, - Set visited, int metaDepth) { - - if (visited.add(element)) { - try { - // Locally declared annotations (ignoring @Inherited) - Annotation[] annotations = AnnotationUtils.getDeclaredAnnotations(element); - if (annotations.length > 0) { - List aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null); - - // Search in local annotations - for (Annotation annotation : annotations) { - Class currentAnnotationType = annotation.annotationType(); - if (!AnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { - if (annotationTypes.contains(currentAnnotationType) || - currentAnnotationType.getName().equals(annotationName) || - processor.alwaysProcesses()) { - T result = processor.process(element, annotation, metaDepth); - if (result != null) { - if (aggregatedResults != null && metaDepth == 0) { - aggregatedResults.add(result); - } - else { - return result; - } - } - } - // Repeatable annotations in container? - else if (currentAnnotationType == containerType) { - for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) { - T result = processor.process(element, contained, metaDepth); - if (aggregatedResults != null && result != null) { - // No need to post-process since repeatable annotations within a - // container cannot be composed annotations. - aggregatedResults.add(result); - } - } - } - } - } - - // Recursively search in meta-annotations - for (Annotation annotation : annotations) { - Class currentAnnotationType = annotation.annotationType(); - if (!AnnotationUtils.hasPlainJavaAnnotationsOnly(currentAnnotationType)) { - T result = searchWithFindSemantics(currentAnnotationType, annotationTypes, annotationName, - containerType, processor, visited, metaDepth + 1); - if (result != null) { - processor.postProcess(currentAnnotationType, annotation, result); - if (aggregatedResults != null && metaDepth == 0) { - aggregatedResults.add(result); - } - else { - return result; - } - } - } - } - - if (!CollectionUtils.isEmpty(aggregatedResults)) { - // Prepend to support top-down ordering within class hierarchies - processor.getAggregatedResults().addAll(0, aggregatedResults); - } - } - - if (element instanceof Method) { - Method method = (Method) element; - T result; - - // Search on possibly bridged method - Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - if (resolvedMethod != method) { - result = searchWithFindSemantics(resolvedMethod, annotationTypes, annotationName, - containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - - // Search on methods in interfaces declared locally - Class[] ifcs = method.getDeclaringClass().getInterfaces(); - if (ifcs.length > 0) { - result = searchOnInterfaces(method, annotationTypes, annotationName, - containerType, processor, visited, metaDepth, ifcs); - if (result != null) { - return result; - } - } - - // Search on methods in class hierarchy and interface hierarchy - Class clazz = method.getDeclaringClass(); - while (true) { - clazz = clazz.getSuperclass(); - if (clazz == null || clazz == Object.class) { - break; - } - Set annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(clazz); - if (!annotatedMethods.isEmpty()) { - for (Method annotatedMethod : annotatedMethods) { - if (AnnotationUtils.isOverride(method, annotatedMethod)) { - Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod); - result = searchWithFindSemantics(resolvedSuperMethod, annotationTypes, - annotationName, containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - } - } - // Search on interfaces declared on superclass - result = searchOnInterfaces(method, annotationTypes, annotationName, - containerType, processor, visited, metaDepth, clazz.getInterfaces()); - if (result != null) { - return result; - } - } - } - else if (element instanceof Class) { - Class clazz = (Class) element; - if (!Annotation.class.isAssignableFrom(clazz)) { - // Search on interfaces - for (Class ifc : clazz.getInterfaces()) { - T result = searchWithFindSemantics(ifc, annotationTypes, annotationName, - containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - // Search on superclass - Class superclass = clazz.getSuperclass(); - if (superclass != null && superclass != Object.class) { - T result = searchWithFindSemantics(superclass, annotationTypes, annotationName, - containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - } - } - } - catch (Throwable ex) { - AnnotationUtils.handleIntrospectionFailure(element, ex); - } - } - return null; - } - - @Nullable - private static T searchOnInterfaces(Method method, Set> annotationTypes, - @Nullable String annotationName, @Nullable Class containerType, - Processor processor, Set visited, int metaDepth, Class[] ifcs) { - - for (Class ifc : ifcs) { - Set annotatedMethods = AnnotationUtils.getAnnotatedMethodsInBaseType(ifc); - if (!annotatedMethods.isEmpty()) { - for (Method annotatedMethod : annotatedMethods) { - if (AnnotationUtils.isOverride(method, annotatedMethod)) { - T result = searchWithFindSemantics(annotatedMethod, annotationTypes, annotationName, - containerType, processor, visited, metaDepth); - if (result != null) { - return result; - } - } - } - } - } - - return null; - } - - /** - * Get the array of raw (unsynthesized) annotations from the {@code value} - * attribute of the supplied repeatable annotation {@code container}. - * @since 4.3 - */ - @SuppressWarnings("unchecked") - private static A[] getRawAnnotationsFromContainer( - @Nullable AnnotatedElement element, Annotation container) { - - try { - A[] value = (A[]) AnnotationUtils.getValue(container); - if (value != null) { - return value; - } - } - catch (Throwable ex) { - AnnotationUtils.handleIntrospectionFailure(element, ex); - } - // Unable to read value from repeating annotation container -> ignore it. - return (A[]) EMPTY_ANNOTATION_ARRAY; - } - - /** - * Resolve the container type for the supplied repeatable {@code annotationType}. - *

Delegates to {@link AnnotationUtils#resolveContainerAnnotationType(Class)}. - * @param annotationType the annotation type to resolve the container for - * @return the container type (never {@code null}) - * @throws IllegalArgumentException if the container type cannot be resolved - * @since 4.3 - */ - private static Class resolveContainerType(Class annotationType) { - Class containerType = AnnotationUtils.resolveContainerAnnotationType(annotationType); - if (containerType == null) { - throw new IllegalArgumentException( - "Annotation type must be a repeatable annotation: failed to resolve container type for " + - annotationType.getName()); - } - return containerType; - } - - /** - * Validate that the supplied {@code containerType} is a proper container - * annotation for the supplied repeatable {@code annotationType} (i.e. - * that it declares a {@code value} attribute that holds an array of the - * {@code annotationType}). - * @throws AnnotationConfigurationException if the supplied {@code containerType} - * is not a valid container annotation for the supplied {@code annotationType} - * @since 4.3 - */ - private static void validateContainerType(Class annotationType, - Class containerType) { - - try { - Method method = containerType.getDeclaredMethod(AnnotationUtils.VALUE); - Class returnType = method.getReturnType(); - if (!returnType.isArray() || returnType.getComponentType() != annotationType) { - String msg = String.format( - "Container type [%s] must declare a 'value' attribute for an array of type [%s]", - containerType.getName(), annotationType.getName()); - throw new AnnotationConfigurationException(msg); - } - } - catch (Throwable ex) { - AnnotationUtils.rethrowAnnotationConfigurationException(ex); - String msg = String.format("Invalid declaration of container type [%s] for repeatable annotation [%s]", - containerType.getName(), annotationType.getName()); - throw new AnnotationConfigurationException(msg, ex); - } - } - - /** - * Post-process the aggregated results into a set of synthesized annotations. - * @param element the annotated element - * @param aggregatedResults the aggregated results for the given element - * @return the set of annotations - */ - @SuppressWarnings("unchecked") - private static Set postProcessAndSynthesizeAggregatedResults( - AnnotatedElement element, List aggregatedResults) { - - Set annotations = new LinkedHashSet<>(); - for (AnnotationAttributes attributes : aggregatedResults) { - AnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); - Class annType = attributes.annotationType(); - if (annType != null) { - annotations.add((A) AnnotationUtils.synthesizeAnnotation(attributes, annType, element)); - } - } - return annotations; - } - - - /** - * Callback interface that is used to process annotations during a search. - *

Depending on the use case, a processor may choose to {@linkplain #process} - * a single target annotation, multiple target annotations, or all annotations - * discovered by the currently executing search. The term "target" in this - * context refers to a matching annotation (i.e. a specific annotation type - * that was found during the search). - *

Returning a non-null value from the {@link #process} method instructs - * the search algorithm to stop searching further; whereas, returning - * {@code null} from the {@link #process} method instructs the search - * algorithm to continue searching for additional annotations. One exception - * to this rule applies to processors that {@linkplain #aggregates aggregate} - * results. If an aggregating processor returns a non-null value, that value - * will be added to the {@linkplain #getAggregatedResults aggregated results} - * and the search algorithm will continue. - *

Processors can optionally {@linkplain #postProcess post-process} the - * result of the {@link #process} method as the search algorithm goes back - * down the annotation hierarchy from an invocation of {@link #process} that - * returned a non-null value down to the {@link AnnotatedElement} that was - * supplied as the starting point to the search algorithm. - * @param the type of result returned by the processor - */ - private interface Processor { - - /** - * Process the supplied annotation. - *

The supplied annotation will be an actual target annotation - * that has been found by the search algorithm, unless this processor - * is configured to {@linkplain #alwaysProcesses always process} - * annotations in which case it may be some other annotation within an - * annotation hierarchy. In the latter case, the {@code metaDepth} - * will have a value greater than {@code 0}. In any case, it is - * up to concrete implementations of this method to decide what to - * do with the supplied annotation. - *

The {@code metaDepth} parameter represents the depth of the - * annotation relative to the first annotated element in the - * annotation hierarchy. For example, an annotation that is - * present on a non-annotation element will have a depth - * of 0; a meta-annotation will have a depth of 1; and a - * meta-meta-annotation will have a depth of 2; etc. - * @param annotatedElement the element that is annotated with the - * supplied annotation, used for contextual logging; may be - * {@code null} if unknown - * @param annotation the annotation to process - * @param metaDepth the meta-depth of the annotation - * @return the result of the processing, or {@code null} to continue - * searching for additional annotations - */ - @Nullable - T process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth); - - /** - * Post-process the result returned by the {@link #process} method. - *

The {@code annotation} supplied to this method is an annotation - * that is present in the annotation hierarchy, between the initial - * {@link AnnotatedElement} and an invocation of {@link #process} - * that returned a non-null value. - * @param annotatedElement the element that is annotated with the - * supplied annotation, used for contextual logging; may be - * {@code null} if unknown - * @param annotation the annotation to post-process - * @param result the result to post-process - */ - void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result); - - /** - * Determine if this processor always processes annotations regardless of - * whether or not the target annotation has been found. - * @return {@code true} if this processor always processes annotations - * @since 4.3 - */ - boolean alwaysProcesses(); - - /** - * Determine if this processor aggregates the results returned by {@link #process}. - *

If this method returns {@code true}, then {@link #getAggregatedResults()} - * must return a non-null value. - * @return {@code true} if this processor supports aggregated results - * @since 4.3 - * @see #getAggregatedResults - */ - boolean aggregates(); - - /** - * Get the list of results aggregated by this processor. - *

NOTE: the processor does not aggregate the results - * itself. Rather, the search algorithm that uses this processor is - * responsible for asking this processor if it {@link #aggregates} results - * and then adding the post-processed results to the list returned by this - * method. - * @return the list of results aggregated by this processor (never {@code null}) - * @since 4.3 - * @see #aggregates - */ - List getAggregatedResults(); - } - - - /** - * {@link Processor} that {@linkplain #process(AnnotatedElement, Annotation, int) - * processes} annotations but does not {@linkplain #postProcess post-process} or - * {@linkplain #aggregates aggregate} results. - * @since 4.2 - */ - private abstract static class SimpleAnnotationProcessor implements Processor { - - private final boolean alwaysProcesses; - - public SimpleAnnotationProcessor() { - this(false); - } - - public SimpleAnnotationProcessor(boolean alwaysProcesses) { - this.alwaysProcesses = alwaysProcesses; - } - - @Override - public final boolean alwaysProcesses() { - return this.alwaysProcesses; - } - - @Override - public final void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result) { - // no-op - } - - @Override - public final boolean aggregates() { - return false; - } - - @Override - public final List getAggregatedResults() { - throw new UnsupportedOperationException("SimpleAnnotationProcessor does not support aggregated results"); - } - } - - - /** - * {@link SimpleAnnotationProcessor} that always returns {@link Boolean#TRUE} when - * asked to {@linkplain #process(AnnotatedElement, Annotation, int) process} an - * annotation. - * @since 4.3 - */ - static class AlwaysTrueBooleanAnnotationProcessor extends SimpleAnnotationProcessor { - - @Override - public final Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { - return Boolean.TRUE; - } - } - - - /** - * {@link Processor} that gets the {@code AnnotationAttributes} for the - * target annotation during the {@link #process} phase and then merges - * annotation attributes from lower levels in the annotation hierarchy - * during the {@link #postProcess} phase. - *

A {@code MergedAnnotationAttributesProcessor} may optionally be - * configured to {@linkplain #aggregates aggregate} results. - * @since 4.2 - * @see AnnotationUtils#retrieveAnnotationAttributes - * @see AnnotationUtils#postProcessAnnotationAttributes - */ - private static class MergedAnnotationAttributesProcessor implements Processor { - - private final boolean classValuesAsString; - - private final boolean nestedAnnotationsAsMap; - - private final boolean aggregates; - - private final List aggregatedResults; - - MergedAnnotationAttributesProcessor() { - this(false, false, false); - } - - MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - this(classValuesAsString, nestedAnnotationsAsMap, false); - } - - MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap, - boolean aggregates) { - - this.classValuesAsString = classValuesAsString; - this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; - this.aggregates = aggregates; - this.aggregatedResults = (aggregates ? new ArrayList<>() : Collections.emptyList()); - } - - @Override - public boolean alwaysProcesses() { - return false; - } - - @Override - public boolean aggregates() { - return this.aggregates; - } - - @Override - public List getAggregatedResults() { - return this.aggregatedResults; - } - - @Override - @Nullable - public AnnotationAttributes process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { - return AnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation, - this.classValuesAsString, this.nestedAnnotationsAsMap); - } - - @Override - public void postProcess(@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) { - annotation = AnnotationUtils.synthesizeAnnotation(annotation, element); - Class targetAnnotationType = attributes.annotationType(); - - // Track which attribute values have already been replaced so that we can short - // circuit the search algorithms. - Set valuesAlreadyReplaced = new HashSet<>(); - - for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) { - String attributeName = attributeMethod.getName(); - String attributeOverrideName = AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType); - - // Explicit annotation attribute override declared via @AliasFor - if (attributeOverrideName != null) { - if (valuesAlreadyReplaced.contains(attributeOverrideName)) { - continue; - } - - List targetAttributeNames = new ArrayList<>(); - targetAttributeNames.add(attributeOverrideName); - valuesAlreadyReplaced.add(attributeOverrideName); - - // Ensure all aliased attributes in the target annotation are overridden. (SPR-14069) - List aliases = AnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName); - if (aliases != null) { - for (String alias : aliases) { - if (!valuesAlreadyReplaced.contains(alias)) { - targetAttributeNames.add(alias); - valuesAlreadyReplaced.add(alias); - } - } - } - - overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames); - } - // Implicit annotation attribute override based on convention - else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) { - overrideAttribute(element, annotation, attributes, attributeName, attributeName); - } - } - } - - private void overrideAttributes(@Nullable AnnotatedElement element, Annotation annotation, - AnnotationAttributes attributes, String sourceAttributeName, List targetAttributeNames) { - - Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName); - - for (String targetAttributeName : targetAttributeNames) { - attributes.put(targetAttributeName, adaptedValue); - } - } - - private void overrideAttribute(@Nullable AnnotatedElement element, Annotation annotation, - AnnotationAttributes attributes, String sourceAttributeName, String targetAttributeName) { - - attributes.put(targetAttributeName, getAdaptedValue(element, annotation, sourceAttributeName)); - } - - @Nullable - private Object getAdaptedValue( - @Nullable AnnotatedElement element, Annotation annotation, String sourceAttributeName) { - - Object value = AnnotationUtils.getValue(annotation, sourceAttributeName); - return AnnotationUtils.adaptValue(element, value, this.classValuesAsString, this.nestedAnnotationsAsMap); - } + return InternalAnnotatedElementUtils.findMergedRepeatableAnnotations(element, + annotationType, containerType); } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 2bf0f2dc24..b6ca487ad5 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -17,38 +17,15 @@ 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.Collection; -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, @@ -120,37 +97,6 @@ public abstract class AnnotationUtils { */ 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; - /** * Determine whether the given class is a candidate for carrying one of the specified @@ -213,21 +159,9 @@ public abstract class AnnotationUtils { * @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; - } + return InternalAnnotationUtils.getAnnotation(annotation, annotationType); } /** @@ -244,22 +178,7 @@ public abstract class AnnotationUtils { */ @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; - } + return InternalAnnotationUtils.getAnnotation(annotatedElement, annotationType); } /** @@ -278,8 +197,7 @@ public abstract class AnnotationUtils { */ @Nullable public static A getAnnotation(Method method, Class annotationType) { - Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); - return getAnnotation((AnnotatedElement) resolvedMethod, annotationType); + return InternalAnnotationUtils.getAnnotation(method, annotationType); } /** @@ -295,13 +213,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Annotation[] getAnnotations(AnnotatedElement annotatedElement) { - try { - return synthesizeAnnotationArray(annotatedElement.getAnnotations(), annotatedElement); - } - catch (Throwable ex) { - handleIntrospectionFailure(annotatedElement, ex); - return null; - } + return InternalAnnotationUtils.getAnnotations(annotatedElement); } /** @@ -318,13 +230,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Annotation[] getAnnotations(Method method) { - try { - return synthesizeAnnotationArray(BridgeMethodResolver.findBridgedMethod(method).getAnnotations(), method); - } - catch (Throwable ex) { - handleIntrospectionFailure(method, ex); - return null; - } + return InternalAnnotationUtils.getAnnotations(method); } /** @@ -356,8 +262,8 @@ public abstract class AnnotationUtils { */ public static Set getRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType) { - - return getRepeatableAnnotations(annotatedElement, annotationType, null); + return InternalAnnotationUtils.getRepeatableAnnotations(annotatedElement, + annotationType); } /** @@ -392,15 +298,8 @@ public abstract class AnnotationUtils { */ 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; + return InternalAnnotationUtils.getRepeatableAnnotations(annotatedElement, + annotationType, containerAnnotationType); } /** @@ -433,8 +332,8 @@ public abstract class AnnotationUtils { */ public static Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, Class annotationType) { - - return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null); + return InternalAnnotationUtils.getDeclaredRepeatableAnnotations(annotatedElement, + annotationType); } /** @@ -469,17 +368,8 @@ public abstract class AnnotationUtils { */ 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(); - } + return InternalAnnotationUtils.getDeclaredRepeatableAnnotations(annotatedElement, + annotationType, containerAnnotationType); } /** @@ -500,44 +390,7 @@ public abstract class AnnotationUtils { */ @Nullable public static A findAnnotation(AnnotatedElement annotatedElement, Class annotationType) { - // 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; + return InternalAnnotationUtils.findAnnotation(annotatedElement, annotationType); } /** @@ -555,173 +408,9 @@ public abstract class AnnotationUtils { * @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(); + return InternalAnnotationUtils.findAnnotation(method, annotationType); } /** @@ -748,84 +437,7 @@ public abstract class AnnotationUtils { */ @Nullable public static A findAnnotation(Class clazz, 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); + return InternalAnnotationUtils.findAnnotation(clazz, annotationType); } /** @@ -852,13 +464,8 @@ public abstract class AnnotationUtils { */ @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()); + return InternalAnnotationUtils.findAnnotationDeclaringClass(annotationType, + clazz); } /** @@ -888,16 +495,8 @@ public abstract class AnnotationUtils { @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()); + return InternalAnnotationUtils.findAnnotationDeclaringClassForTypes( + annotationTypes, clazz); } /** @@ -919,13 +518,7 @@ public abstract class AnnotationUtils { * @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; - } + return InternalAnnotationUtils.isAnnotationDeclaredLocally(annotationType, clazz); } /** @@ -948,7 +541,7 @@ public abstract class AnnotationUtils { * @see #isAnnotationDeclaredLocally(Class, Class) */ public static boolean isAnnotationInherited(Class annotationType, Class clazz) { - return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz)); + return InternalAnnotationUtils.isAnnotationInherited(annotationType, clazz); } /** @@ -961,46 +554,8 @@ public abstract class AnnotationUtils { */ 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.")); + return InternalAnnotationUtils.isAnnotationMetaPresent(annotationType, + metaAnnotationType); } /** @@ -1010,18 +565,7 @@ public abstract class AnnotationUtils { * @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())); + return InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotation); } /** @@ -1032,7 +576,7 @@ public abstract class AnnotationUtils { * @since 4.2 */ public static boolean isInJavaLangAnnotationPackage(@Nullable String annotationType) { - return (annotationType != null && annotationType.startsWith("java.lang.annotation")); + return InternalAnnotationUtils.isInJavaLangAnnotationPackage(annotationType); } /** @@ -1048,17 +592,7 @@ public abstract class AnnotationUtils { * @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); - } - } - } + InternalAnnotationUtils.validateAnnotation(annotation); } /** @@ -1077,7 +611,7 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static Map getAnnotationAttributes(Annotation annotation) { - return getAnnotationAttributes(null, annotation); + return InternalAnnotationUtils.getAnnotationAttributes(annotation); } /** @@ -1095,7 +629,8 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(Annotation, boolean, boolean) */ public static Map getAnnotationAttributes(Annotation annotation, boolean classValuesAsString) { - return getAnnotationAttributes(annotation, classValuesAsString, false); + return InternalAnnotationUtils.getAnnotationAttributes(annotation, + classValuesAsString); } /** @@ -1116,8 +651,8 @@ public abstract class AnnotationUtils { */ public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { - - return getAnnotationAttributes(null, annotation, classValuesAsString, nestedAnnotationsAsMap); + return InternalAnnotationUtils.getAnnotationAttributes(annotation, + classValuesAsString, nestedAnnotationsAsMap); } /** @@ -1134,7 +669,8 @@ public abstract class AnnotationUtils { * @see #getAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean) */ public static AnnotationAttributes getAnnotationAttributes(@Nullable AnnotatedElement annotatedElement, Annotation annotation) { - return getAnnotationAttributes(annotatedElement, annotation, false, false); + return InternalAnnotationUtils.getAnnotationAttributes(annotatedElement, + annotation); } /** @@ -1157,137 +693,8 @@ public abstract class AnnotationUtils { */ 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; + return InternalAnnotationUtils.getAnnotationAttributes(annotatedElement, + annotation, classValuesAsString, nestedAnnotationsAsMap); } /** @@ -1297,31 +704,7 @@ public abstract class AnnotationUtils { * @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)); - } - } - } + InternalAnnotationUtils.registerDefaultValues(attributes); } /** @@ -1343,104 +726,8 @@ public abstract class AnnotationUtils { */ 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)); - } - } + InternalAnnotationUtils.postProcessAnnotationAttributes(annotatedElement, + attributes, classValuesAsString); } /** @@ -1454,7 +741,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getValue(Annotation annotation) { - return getValue(annotation, VALUE); + return InternalAnnotationUtils.getValue(annotation); } /** @@ -1469,26 +756,7 @@ public abstract class AnnotationUtils { */ @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; - } + return InternalAnnotationUtils.getValue(annotation, attributeName); } /** @@ -1500,7 +768,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue(Annotation annotation) { - return getDefaultValue(annotation, VALUE); + return InternalAnnotationUtils.getDefaultValue(annotation); } /** @@ -1512,10 +780,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue(@Nullable Annotation annotation, @Nullable String attributeName) { - if (annotation == null) { - return null; - } - return getDefaultValue(annotation.annotationType(), attributeName); + return InternalAnnotationUtils.getDefaultValue(annotation, attributeName); } /** @@ -1527,7 +792,7 @@ public abstract class AnnotationUtils { */ @Nullable public static Object getDefaultValue(Class annotationType) { - return getDefaultValue(annotationType, VALUE); + return InternalAnnotationUtils.getDefaultValue(annotationType); } /** @@ -1541,35 +806,7 @@ public abstract class AnnotationUtils { @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); + return InternalAnnotationUtils.getDefaultValue(annotationType, attributeName); } /** @@ -1591,29 +828,7 @@ public abstract class AnnotationUtils { */ 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); + return InternalAnnotationUtils.synthesizeAnnotation(annotation, annotatedElement); } /** @@ -1645,16 +860,10 @@ public abstract class AnnotationUtils { * @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); + return InternalAnnotationUtils.synthesizeAnnotation(attributes, annotationType, + annotatedElement); } /** @@ -1673,7 +882,7 @@ public abstract class AnnotationUtils { * @see #synthesizeAnnotation(Annotation, AnnotatedElement) */ public static A synthesizeAnnotation(Class annotationType) { - return synthesizeAnnotation(Collections.emptyMap(), annotationType, null); + return InternalAnnotationUtils.synthesizeAnnotation(annotationType); } /** @@ -1692,330 +901,11 @@ public abstract class AnnotationUtils { * @see #synthesizeAnnotation(Annotation, AnnotatedElement) * @see #synthesizeAnnotation(Map, Class, AnnotatedElement) */ - static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, @Nullable Object annotatedElement) { - if (hasPlainJavaAnnotationsOnly(annotatedElement)) { - return annotations; - } + static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, + @Nullable Object annotatedElement) { - 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); - } - } + return InternalAnnotationUtils.synthesizeAnnotationArray(annotations, + annotatedElement); } /** @@ -2023,423 +913,7 @@ public abstract class AnnotationUtils { * @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; - } + InternalAnnotationUtils.clearCache(); } } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotatedElementUtils.java new file mode 100644 index 0000000000..61cad7aef1 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotatedElementUtils.java @@ -0,0 +1,1596 @@ +/* + * 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 + * + * http://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.reflect.AnnotatedElement; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.core.BridgeMethodResolver; +import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * Internal class containing the version of {@link AnnotatedElementUtils} that + * shipped with Spring Framework 5.1. + * + * @author Phillip Webb + * @author Juergen Hoeller + * @author Sam Brannen + * @see AliasFor + * @see AnnotationAttributes + * @see InternalAnnotationUtils + * @see BridgeMethodResolver + */ +abstract class InternalAnnotatedElementUtils { + + /** + * {@code null} constant used to denote that the search algorithm should continue. + */ + @Nullable + private static final Boolean CONTINUE = null; + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; + + private static final Processor alwaysTrueAnnotationProcessor = new AlwaysTrueBooleanAnnotationProcessor(); + + + /** + * Build an adapted {@link AnnotatedElement} for the given annotations, + * typically for use with other methods on {@link InternalAnnotatedElementUtils}. + * @param annotations the annotations to expose through the {@code AnnotatedElement} + * @since 4.3 + */ + public static AnnotatedElement forAnnotations(final Annotation... annotations) { + return new AnnotatedElement() { + @Override + @SuppressWarnings("unchecked") + @Nullable + public T getAnnotation(Class annotationClass) { + for (Annotation ann : annotations) { + if (ann.annotationType() == annotationClass) { + return (T) ann; + } + } + return null; + } + @Override + public Annotation[] getAnnotations() { + return annotations; + } + @Override + public Annotation[] getDeclaredAnnotations() { + return annotations; + } + }; + } + + /** + * Get the fully qualified class names of all meta-annotation types + * present on the annotation (of the specified {@code annotationType}) + * on the supplied {@link AnnotatedElement}. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the annotation type on which to find meta-annotations + * @return the names of all meta-annotations present on the annotation, + * or {@code null} if not found + * @since 4.2 + * @see #getMetaAnnotationTypes(AnnotatedElement, String) + * @see #hasMetaAnnotationTypes + */ + public static Set getMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { + return getMetaAnnotationTypes(element, element.getAnnotation(annotationType)); + } + + /** + * Get the fully qualified class names of all meta-annotation + * types present on the annotation (of the specified + * {@code annotationName}) on the supplied {@link AnnotatedElement}. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation + * type on which to find meta-annotations + * @return the names of all meta-annotations present on the annotation, + * or an empty set if none found + * @see #getMetaAnnotationTypes(AnnotatedElement, Class) + * @see #hasMetaAnnotationTypes + */ + public static Set getMetaAnnotationTypes(AnnotatedElement element, String annotationName) { + return getMetaAnnotationTypes(element, InternalAnnotationUtils.getAnnotation(element, annotationName)); + } + + private static Set getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation composed) { + if (composed == null) { + return Collections.emptySet(); + } + + try { + final Set types = new LinkedHashSet<>(); + searchWithGetSemantics(composed.annotationType(), Collections.emptySet(), null, null, new SimpleAnnotationProcessor(true) { + @Override + @Nullable + public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { + types.add(annotation.annotationType().getName()); + return CONTINUE; + } + }, new HashSet<>(), 1); + return types; + } + catch (Throwable ex) { + InternalAnnotationUtils.rethrowAnnotationConfigurationException(ex); + throw new IllegalStateException("Failed to introspect annotations on " + element, ex); + } + } + + /** + * Determine if the supplied {@link AnnotatedElement} is annotated with + * a composed annotation that is meta-annotated with an + * annotation of the specified {@code annotationType}. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the meta-annotation type to find + * @return {@code true} if a matching meta-annotation is present + * @since 4.2.3 + * @see #getMetaAnnotationTypes + */ + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, Class annotationType) { + return hasMetaAnnotationTypes(element, annotationType, null); + } + + /** + * Determine if the supplied {@link AnnotatedElement} is annotated with a + * composed annotation that is meta-annotated with an annotation + * of the specified {@code annotationName}. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the + * meta-annotation type to find + * @return {@code true} if a matching meta-annotation is present + * @see #getMetaAnnotationTypes + */ + public static boolean hasMetaAnnotationTypes(AnnotatedElement element, String annotationName) { + return hasMetaAnnotationTypes(element, null, annotationName); + } + + private static boolean hasMetaAnnotationTypes( + AnnotatedElement element, @Nullable Class annotationType, @Nullable String annotationName) { + + return Boolean.TRUE.equals( + searchWithGetSemantics(element, annotationType, annotationName, new SimpleAnnotationProcessor() { + @Override + @Nullable + public Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { + return (metaDepth > 0 ? Boolean.TRUE : CONTINUE); + } + })); + } + + /** + * Determine if an annotation of the specified {@code annotationType} + * is present on the supplied {@link AnnotatedElement} or + * within the annotation hierarchy above the specified element. + *

If this method returns {@code true}, then {@link #getMergedAnnotationAttributes} + * will return a non-null value. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @return {@code true} if a matching annotation is present + * @since 4.2.3 + * @see #hasAnnotation(AnnotatedElement, Class) + */ + public static boolean isAnnotated(AnnotatedElement element, Class annotationType) { + // Shortcut: directly present on the element, with no processing needed? + if (element.isAnnotationPresent(annotationType)) { + return true; + } + return Boolean.TRUE.equals(searchWithGetSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor)); + } + + /** + * Determine if an annotation of the specified {@code annotationName} is + * present on the supplied {@link AnnotatedElement} or within the + * annotation hierarchy above the specified element. + *

If this method returns {@code true}, then {@link #getMergedAnnotationAttributes} + * will return a non-null value. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @return {@code true} if a matching annotation is present + */ + public static boolean isAnnotated(AnnotatedElement element, String annotationName) { + return Boolean.TRUE.equals(searchWithGetSemantics(element, null, annotationName, alwaysTrueAnnotationProcessor)); + } + + /** + * Get the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied {@code element} and + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy. + *

{@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #getMergedAnnotation(AnnotatedElement, Class) + * @see #findMergedAnnotation(AnnotatedElement, Class) + */ + @Nullable + public static AnnotationAttributes getMergedAnnotationAttributes( + AnnotatedElement element, Class annotationType) { + + AnnotationAttributes attributes = searchWithGetSemantics(element, annotationType, null, + new MergedAnnotationAttributesProcessor()); + InternalAnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); + return attributes; + } + + /** + * Get the first annotation of the specified {@code annotationName} within + * the annotation hierarchy above the supplied {@code element} and + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy. + *

{@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean)}, + * supplying {@code false} for {@code classValuesAsString} and {@code nestedAnnotationsAsMap}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #getAllAnnotationAttributes(AnnotatedElement, String) + */ + @Nullable + public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, String annotationName) { + return getMergedAnnotationAttributes(element, annotationName, false, false); + } + + /** + * Get the first annotation of the specified {@code annotationName} within + * the annotation hierarchy above the supplied {@code element} and + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy. + *

Attributes from lower levels in the annotation hierarchy override attributes + * of the same name from higher levels, and {@link AliasFor @AliasFor} semantics are + * fully supported, both within a single annotation and within the annotation hierarchy. + *

In contrast to {@link #getAllAnnotationAttributes}, the search algorithm used by + * this method will stop searching the annotation hierarchy once the first annotation + * of the specified {@code annotationName} has been found. As a consequence, + * additional annotations of the specified {@code annotationName} will be ignored. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @param classValuesAsString whether to convert Class references into Strings or to + * preserve them as Class references + * @param nestedAnnotationsAsMap whether to convert nested Annotation instances + * into {@code AnnotationAttributes} maps or to preserve them as Annotation instances + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + */ + @Nullable + public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element, + String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName, + new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); + InternalAnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); + return attributes; + } + + /** + * Get the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied {@code element}, + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy, and synthesize + * the result back into an annotation of the specified {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

This method delegates to {@link #getMergedAnnotationAttributes(AnnotatedElement, Class)} + * and {@link InternalAnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement)}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @return the merged, synthesized {@code Annotation}, or {@code null} if not found + * @since 4.2 + * @see #getMergedAnnotationAttributes(AnnotatedElement, Class) + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see InternalAnnotationUtils#synthesizeAnnotation(Map, Class, AnnotatedElement) + */ + @Nullable + public static A getMergedAnnotation(AnnotatedElement element, Class annotationType) { + // Shortcut: directly present on the element, with no merging needed? + A annotation = element.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return InternalAnnotationUtils.synthesizeAnnotation(annotation, element); + } + + // Shortcut: no searchable annotations to be found on plain Java classes and org.springframework.lang types... + if (InternalAnnotationUtils.hasPlainJavaAnnotationsOnly(element)) { + return null; + } + + // Exhaustive retrieval of merged annotation attributes... + AnnotationAttributes attributes = getMergedAnnotationAttributes(element, annotationType); + return (attributes != null ? InternalAnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); + } + + /** + * Get all annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + * @since 4.3 + * @see #getMergedAnnotation(AnnotatedElement, Class) + * @see #getAllAnnotationAttributes(AnnotatedElement, String) + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set getAllMergedAnnotations(AnnotatedElement element, Class annotationType) { + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithGetSemantics(element, annotationType, null, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Get all annotations of the specified {@code annotationTypes} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the + * annotation hierarchy and synthesize the results back into an annotation + * of the corresponding {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationTypes the annotation types to find + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + * @since 5.1 + * @see #getAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set getAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithGetSemantics(element, annotationTypes, null, null, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Get all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

The container type that holds the repeatable annotations will be looked up + * via {@link java.lang.annotation.Repeatable}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged repeatable {@code Annotations} found, + * or an empty set if none were found + * @throws IllegalArgumentException if the {@code element} or {@code annotationType} + * is {@code null}, or if the container type cannot be resolved + * @since 4.3 + * @see #getMergedAnnotation(AnnotatedElement, Class) + * @see #getAllMergedAnnotations(AnnotatedElement, Class) + * @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class) + */ + public static Set getMergedRepeatableAnnotations(AnnotatedElement element, + Class annotationType) { + + return getMergedRepeatableAnnotations(element, annotationType, null); + } + + /** + * Get all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @param containerType the type of the container that holds the annotations; + * may be {@code null} if the container type should be looked up via + * {@link java.lang.annotation.Repeatable} + * @return the set of all merged repeatable {@code Annotations} found, + * or an empty set if none were found + * @throws IllegalArgumentException if the {@code element} or {@code annotationType} + * is {@code null}, or if the container type cannot be resolved + * @throws AnnotationConfigurationException if the supplied {@code containerType} + * is not a valid container annotation for the supplied {@code annotationType} + * @since 4.3 + * @see #getMergedAnnotation(AnnotatedElement, Class) + * @see #getAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set getMergedRepeatableAnnotations(AnnotatedElement element, + Class annotationType, @Nullable Class containerType) { + + if (containerType == null) { + containerType = resolveContainerType(annotationType); + } + else { + validateContainerType(annotationType, containerType); + } + + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithGetSemantics(element, Collections.singleton(annotationType), null, containerType, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Get the annotation attributes of all annotations of the specified + * {@code annotationName} in the annotation hierarchy above the supplied + * {@link AnnotatedElement} and store the results in a {@link MultiValueMap}. + *

Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)}, + * this method does not support attribute overrides. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation + * attributes from all annotations found, or {@code null} if not found + * @see #getAllAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + */ + @Nullable + public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, String annotationName) { + return getAllAnnotationAttributes(element, annotationName, false, false); + } + + /** + * Get the annotation attributes of all annotations of + * the specified {@code annotationName} in the annotation hierarchy above + * the supplied {@link AnnotatedElement} and store the results in a + * {@link MultiValueMap}. + *

Note: in contrast to {@link #getMergedAnnotationAttributes(AnnotatedElement, String)}, + * this method does not support attribute overrides. + *

This method follows get semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @param classValuesAsString whether to convert Class references into Strings or to + * preserve them as Class references + * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into + * {@code AnnotationAttributes} maps or to preserve them as Annotation instances + * @return a {@link MultiValueMap} keyed by attribute name, containing the annotation + * attributes from all annotations found, or {@code null} if not found + */ + @Nullable + public static MultiValueMap getAllAnnotationAttributes(AnnotatedElement element, + String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) { + + final MultiValueMap attributesMap = new LinkedMultiValueMap<>(); + + searchWithGetSemantics(element, null, annotationName, new SimpleAnnotationProcessor() { + @Override + @Nullable + public Object process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { + AnnotationAttributes annotationAttributes = InternalAnnotationUtils.getAnnotationAttributes( + annotation, classValuesAsString, nestedAnnotationsAsMap); + annotationAttributes.forEach(attributesMap::add); + return CONTINUE; + } + }); + + return (!attributesMap.isEmpty() ? attributesMap : null); + } + + /** + * Determine if an annotation of the specified {@code annotationType} + * is available on the supplied {@link AnnotatedElement} or + * within the annotation hierarchy above the specified element. + *

If this method returns {@code true}, then {@link #findMergedAnnotationAttributes} + * will return a non-null value. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @return {@code true} if a matching annotation is present + * @since 4.3 + * @see #isAnnotated(AnnotatedElement, Class) + */ + public static boolean hasAnnotation(AnnotatedElement element, Class annotationType) { + // Shortcut: directly present on the element, with no processing needed? + if (element.isAnnotationPresent(annotationType)) { + return true; + } + return Boolean.TRUE.equals(searchWithFindSemantics(element, annotationType, null, alwaysTrueAnnotationProcessor)); + } + + /** + * Find the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied {@code element} and + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy. + *

Attributes from lower levels in the annotation hierarchy override + * attributes of the same name from higher levels, and + * {@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

In contrast to {@link #getAllAnnotationAttributes}, the search algorithm + * used by this method will stop searching the annotation hierarchy once the + * first annotation of the specified {@code annotationType} has been found. + * As a consequence, additional annotations of the specified + * {@code annotationType} will be ignored. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param classValuesAsString whether to convert Class references into + * Strings or to preserve them as Class references + * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into + * {@code AnnotationAttributes} maps or to preserve them as Annotation instances + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + */ + @Nullable + public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, + Class annotationType, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + AnnotationAttributes attributes = searchWithFindSemantics(element, annotationType, null, + new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); + InternalAnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); + return attributes; + } + + /** + * Find the first annotation of the specified {@code annotationName} within + * the annotation hierarchy above the supplied {@code element} and + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy. + *

Attributes from lower levels in the annotation hierarchy override + * attributes of the same name from higher levels, and + * {@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

In contrast to {@link #getAllAnnotationAttributes}, the search + * algorithm used by this method will stop searching the annotation + * hierarchy once the first annotation of the specified + * {@code annotationName} has been found. As a consequence, additional + * annotations of the specified {@code annotationName} will be ignored. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationName the fully qualified class name of the annotation type to find + * @param classValuesAsString whether to convert Class references into Strings or to + * preserve them as Class references + * @param nestedAnnotationsAsMap whether to convert nested Annotation instances into + * {@code AnnotationAttributes} maps or to preserve them as Annotation instances + * @return the merged {@code AnnotationAttributes}, or {@code null} if not found + * @since 4.2 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #getMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + */ + @Nullable + public static AnnotationAttributes findMergedAnnotationAttributes(AnnotatedElement element, + String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + + AnnotationAttributes attributes = searchWithFindSemantics(element, null, annotationName, + new MergedAnnotationAttributesProcessor(classValuesAsString, nestedAnnotationsAsMap)); + InternalAnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap); + return attributes; + } + + /** + * Find the first annotation of the specified {@code annotationType} within + * the annotation hierarchy above the supplied {@code element}, + * merge that annotation's attributes with matching attributes from + * annotations in lower levels of the annotation hierarchy, and synthesize + * the result back into an annotation of the specified {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both + * within a single annotation and within the annotation hierarchy. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element + * @param annotationType the annotation type to find + * @return the merged, synthesized {@code Annotation}, or {@code null} if not found + * @since 4.2 + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + * @see #findMergedAnnotationAttributes(AnnotatedElement, String, boolean, boolean) + * @see #getMergedAnnotationAttributes(AnnotatedElement, Class) + */ + @Nullable + public static A findMergedAnnotation(AnnotatedElement element, Class annotationType) { + // Shortcut: directly present on the element, with no merging needed? + A annotation = element.getDeclaredAnnotation(annotationType); + if (annotation != null) { + return InternalAnnotationUtils.synthesizeAnnotation(annotation, element); + } + + // Shortcut: no searchable annotations to be found on plain Java classes and org.springframework.lang types... + if (InternalAnnotationUtils.hasPlainJavaAnnotationsOnly(element)) { + return null; + } + + // Exhaustive retrieval of merged annotation attributes... + AnnotationAttributes attributes = findMergedAnnotationAttributes(element, annotationType, false, false); + return (attributes != null ? InternalAnnotationUtils.synthesizeAnnotation(attributes, annotationType, element) : null); + } + + /** + * Find all annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + * @since 4.3 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #getAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set findAllMergedAnnotations(AnnotatedElement element, Class annotationType) { + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithFindSemantics(element, annotationType, null, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Find all annotations of the specified {@code annotationTypes} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the + * annotation hierarchy and synthesize the results back into an annotation + * of the corresponding {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationTypes the annotation types to find + * @return the set of all merged, synthesized {@code Annotations} found, + * or an empty set if none were found + * @since 5.1 + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set findAllMergedAnnotations(AnnotatedElement element, Set> annotationTypes) { + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithFindSemantics(element, annotationTypes, null, null, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Find all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

The container type that holds the repeatable annotations will be looked up + * via {@link java.lang.annotation.Repeatable}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @return the set of all merged repeatable {@code Annotations} found, + * or an empty set if none were found + * @throws IllegalArgumentException if the {@code element} or {@code annotationType} + * is {@code null}, or if the container type cannot be resolved + * @since 4.3 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + * @see #findMergedRepeatableAnnotations(AnnotatedElement, Class, Class) + */ + public static Set findMergedRepeatableAnnotations(AnnotatedElement element, + Class annotationType) { + + return findMergedRepeatableAnnotations(element, annotationType, null); + } + + /** + * Find all repeatable annotations of the specified {@code annotationType} + * within the annotation hierarchy above the supplied {@code element}; + * and for each annotation found, merge that annotation's attributes with + * matching attributes from annotations in lower levels of the annotation + * hierarchy and synthesize the results back into an annotation of the specified + * {@code annotationType}. + *

{@link AliasFor @AliasFor} semantics are fully supported, both within a + * single annotation and within annotation hierarchies. + *

This method follows find semantics as described in the + * {@linkplain InternalAnnotatedElementUtils class-level javadoc}. + * @param element the annotated element (never {@code null}) + * @param annotationType the annotation type to find (never {@code null}) + * @param containerType the type of the container that holds the annotations; + * may be {@code null} if the container type should be looked up via + * {@link java.lang.annotation.Repeatable} + * @return the set of all merged repeatable {@code Annotations} found, + * or an empty set if none were found + * @throws IllegalArgumentException if the {@code element} or {@code annotationType} + * is {@code null}, or if the container type cannot be resolved + * @throws AnnotationConfigurationException if the supplied {@code containerType} + * is not a valid container annotation for the supplied {@code annotationType} + * @since 4.3 + * @see #findMergedAnnotation(AnnotatedElement, Class) + * @see #findAllMergedAnnotations(AnnotatedElement, Class) + */ + public static Set findMergedRepeatableAnnotations(AnnotatedElement element, + Class annotationType, @Nullable Class containerType) { + + if (containerType == null) { + containerType = resolveContainerType(annotationType); + } + else { + validateContainerType(annotationType, containerType); + } + + MergedAnnotationAttributesProcessor processor = new MergedAnnotationAttributesProcessor(false, false, true); + searchWithFindSemantics(element, Collections.singleton(annotationType), null, containerType, processor); + return postProcessAndSynthesizeAggregatedResults(element, processor.getAggregatedResults()); + } + + /** + * Search for annotations of the specified {@code annotationName} or + * {@code annotationType} on the specified {@code element}, following + * get semantics. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param processor the processor to delegate to + * @return the result of the processor (potentially {@code null}) + */ + @Nullable + private static T searchWithGetSemantics(AnnotatedElement element, + @Nullable Class annotationType, + @Nullable String annotationName, Processor processor) { + + return searchWithGetSemantics(element, + (annotationType != null ? Collections.singleton(annotationType) : Collections.emptySet()), + annotationName, null, processor); + } + + /** + * Search for annotations of the specified {@code annotationName} or + * {@code annotationType} on the specified {@code element}, following + * get semantics. + * @param element the annotated element + * @param annotationTypes the annotation types to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param containerType the type of the container that holds repeatable + * annotations, or {@code null} if the annotation is not repeatable + * @param processor the processor to delegate to + * @return the result of the processor (potentially {@code null}) + * @since 4.3 + */ + @Nullable + private static T searchWithGetSemantics(AnnotatedElement element, + Set> annotationTypes, @Nullable String annotationName, + @Nullable Class containerType, Processor processor) { + + try { + return searchWithGetSemantics(element, annotationTypes, annotationName, containerType, processor, + new HashSet<>(), 0); + } + catch (Throwable ex) { + InternalAnnotationUtils.rethrowAnnotationConfigurationException(ex); + throw new IllegalStateException("Failed to introspect annotations on " + element, ex); + } + } + + /** + * Perform the search algorithm for the {@link #searchWithGetSemantics} + * method, avoiding endless recursion by tracking which annotated elements + * have already been visited. + *

The {@code metaDepth} parameter is explained in the + * {@link Processor#process process()} method of the {@link Processor} API. + * @param element the annotated element + * @param annotationTypes the annotation types to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param containerType the type of the container that holds repeatable + * annotations, or {@code null} if the annotation is not repeatable + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the meta-depth of the annotation + * @return the result of the processor (potentially {@code null}) + */ + @Nullable + private static T searchWithGetSemantics(AnnotatedElement element, + Set> annotationTypes, @Nullable String annotationName, + @Nullable Class containerType, Processor processor, + Set visited, int metaDepth) { + + if (visited.add(element)) { + try { + // Start searching within locally declared annotations + List declaredAnnotations = Arrays.asList(InternalAnnotationUtils.getDeclaredAnnotations(element)); + T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations, + annotationTypes, annotationName, containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + + if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new + Class superclass = ((Class) element).getSuperclass(); + if (superclass != null && superclass != Object.class) { + List inheritedAnnotations = new LinkedList<>(); + for (Annotation annotation : element.getAnnotations()) { + if (!declaredAnnotations.contains(annotation)) { + inheritedAnnotations.add(annotation); + } + } + // Continue searching within inherited annotations + result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations, + annotationTypes, annotationName, containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + catch (Throwable ex) { + InternalAnnotationUtils.handleIntrospectionFailure(element, ex); + } + } + + return null; + } + + /** + * This method is invoked by {@link #searchWithGetSemantics} to perform + * the actual search within the supplied list of annotations. + *

This method should be invoked first with locally declared annotations + * and then subsequently with inherited annotations, thereby allowing + * local annotations to take precedence over inherited annotations. + *

The {@code metaDepth} parameter is explained in the + * {@link Processor#process process()} method of the {@link Processor} API. + * @param element the element that is annotated with the supplied + * annotations, used for contextual logging; may be {@code null} if unknown + * @param annotations the annotations to search in + * @param annotationTypes the annotation types to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param containerType the type of the container that holds repeatable + * annotations, or {@code null} if the annotation is not repeatable + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the meta-depth of the annotation + * @return the result of the processor (potentially {@code null}) + * @since 4.2 + */ + @Nullable + private static T searchWithGetSemanticsInAnnotations(@Nullable AnnotatedElement element, + List annotations, Set> annotationTypes, + @Nullable String annotationName, @Nullable Class containerType, + Processor processor, Set visited, int metaDepth) { + + // Search in annotations + for (Annotation annotation : annotations) { + Class currentAnnotationType = annotation.annotationType(); + if (!InternalAnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { + if (annotationTypes.contains(currentAnnotationType) || + currentAnnotationType.getName().equals(annotationName) || + processor.alwaysProcesses()) { + T result = processor.process(element, annotation, metaDepth); + if (result != null) { + if (processor.aggregates() && metaDepth == 0) { + processor.getAggregatedResults().add(result); + } + else { + return result; + } + } + } + // Repeatable annotations in container? + else if (currentAnnotationType == containerType) { + for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) { + T result = processor.process(element, contained, metaDepth); + if (result != null) { + // No need to post-process since repeatable annotations within a + // container cannot be composed annotations. + processor.getAggregatedResults().add(result); + } + } + } + } + } + + // Recursively search in meta-annotations + for (Annotation annotation : annotations) { + Class currentAnnotationType = annotation.annotationType(); + if (!InternalAnnotationUtils.hasPlainJavaAnnotationsOnly(currentAnnotationType)) { + T result = searchWithGetSemantics(currentAnnotationType, annotationTypes, + annotationName, containerType, processor, visited, metaDepth + 1); + if (result != null) { + processor.postProcess(element, annotation, result); + if (processor.aggregates() && metaDepth == 0) { + processor.getAggregatedResults().add(result); + } + else { + return result; + } + } + } + } + + return null; + } + + /** + * Search for annotations of the specified {@code annotationName} or + * {@code annotationType} on the specified {@code element}, following + * find semantics. + * @param element the annotated element + * @param annotationType the annotation type to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param processor the processor to delegate to + * @return the result of the processor (potentially {@code null}) + * @since 4.2 + */ + @Nullable + private static T searchWithFindSemantics(AnnotatedElement element, + @Nullable Class annotationType, + @Nullable String annotationName, Processor processor) { + + return searchWithFindSemantics(element, + (annotationType != null ? Collections.singleton(annotationType) : Collections.emptySet()), + annotationName, null, processor); + } + + /** + * Search for annotations of the specified {@code annotationName} or + * {@code annotationType} on the specified {@code element}, following + * find semantics. + * @param element the annotated element + * @param annotationTypes the annotation types to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param containerType the type of the container that holds repeatable + * annotations, or {@code null} if the annotation is not repeatable + * @param processor the processor to delegate to + * @return the result of the processor (potentially {@code null}) + * @since 4.3 + */ + @Nullable + private static T searchWithFindSemantics(AnnotatedElement element, + Set> annotationTypes, @Nullable String annotationName, + @Nullable Class containerType, Processor processor) { + + if (containerType != null && !processor.aggregates()) { + throw new IllegalArgumentException( + "Searches for repeatable annotations must supply an aggregating Processor"); + } + + try { + return searchWithFindSemantics( + element, annotationTypes, annotationName, containerType, processor, new HashSet<>(), 0); + } + catch (Throwable ex) { + InternalAnnotationUtils.rethrowAnnotationConfigurationException(ex); + throw new IllegalStateException("Failed to introspect annotations on " + element, ex); + } + } + + /** + * Perform the search algorithm for the {@link #searchWithFindSemantics} + * method, avoiding endless recursion by tracking which annotated elements + * have already been visited. + *

The {@code metaDepth} parameter is explained in the + * {@link Processor#process process()} method of the {@link Processor} API. + * @param element the annotated element (never {@code null}) + * @param annotationTypes the annotation types to find + * @param annotationName the fully qualified class name of the annotation + * type to find (as an alternative to {@code annotationType}) + * @param containerType the type of the container that holds repeatable + * annotations, or {@code null} if the annotation is not repeatable + * @param processor the processor to delegate to + * @param visited the set of annotated elements that have already been visited + * @param metaDepth the meta-depth of the annotation + * @return the result of the processor (potentially {@code null}) + * @since 4.2 + */ + @Nullable + private static T searchWithFindSemantics(AnnotatedElement element, + Set> annotationTypes, @Nullable String annotationName, + @Nullable Class containerType, Processor processor, + Set visited, int metaDepth) { + + if (visited.add(element)) { + try { + // Locally declared annotations (ignoring @Inherited) + Annotation[] annotations = InternalAnnotationUtils.getDeclaredAnnotations(element); + if (annotations.length > 0) { + List aggregatedResults = (processor.aggregates() ? new ArrayList<>() : null); + + // Search in local annotations + for (Annotation annotation : annotations) { + Class currentAnnotationType = annotation.annotationType(); + if (!InternalAnnotationUtils.isInJavaLangAnnotationPackage(currentAnnotationType)) { + if (annotationTypes.contains(currentAnnotationType) || + currentAnnotationType.getName().equals(annotationName) || + processor.alwaysProcesses()) { + T result = processor.process(element, annotation, metaDepth); + if (result != null) { + if (aggregatedResults != null && metaDepth == 0) { + aggregatedResults.add(result); + } + else { + return result; + } + } + } + // Repeatable annotations in container? + else if (currentAnnotationType == containerType) { + for (Annotation contained : getRawAnnotationsFromContainer(element, annotation)) { + T result = processor.process(element, contained, metaDepth); + if (aggregatedResults != null && result != null) { + // No need to post-process since repeatable annotations within a + // container cannot be composed annotations. + aggregatedResults.add(result); + } + } + } + } + } + + // Recursively search in meta-annotations + for (Annotation annotation : annotations) { + Class currentAnnotationType = annotation.annotationType(); + if (!InternalAnnotationUtils.hasPlainJavaAnnotationsOnly(currentAnnotationType)) { + T result = searchWithFindSemantics(currentAnnotationType, annotationTypes, annotationName, + containerType, processor, visited, metaDepth + 1); + if (result != null) { + processor.postProcess(currentAnnotationType, annotation, result); + if (aggregatedResults != null && metaDepth == 0) { + aggregatedResults.add(result); + } + else { + return result; + } + } + } + } + + if (!CollectionUtils.isEmpty(aggregatedResults)) { + // Prepend to support top-down ordering within class hierarchies + processor.getAggregatedResults().addAll(0, aggregatedResults); + } + } + + if (element instanceof Method) { + Method method = (Method) element; + T result; + + // Search on possibly bridged method + Method resolvedMethod = BridgeMethodResolver.findBridgedMethod(method); + if (resolvedMethod != method) { + result = searchWithFindSemantics(resolvedMethod, annotationTypes, annotationName, + containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + + // Search on methods in interfaces declared locally + Class[] ifcs = method.getDeclaringClass().getInterfaces(); + if (ifcs.length > 0) { + result = searchOnInterfaces(method, annotationTypes, annotationName, + containerType, processor, visited, metaDepth, ifcs); + if (result != null) { + return result; + } + } + + // Search on methods in class hierarchy and interface hierarchy + Class clazz = method.getDeclaringClass(); + while (true) { + clazz = clazz.getSuperclass(); + if (clazz == null || clazz == Object.class) { + break; + } + Set annotatedMethods = InternalAnnotationUtils.getAnnotatedMethodsInBaseType(clazz); + if (!annotatedMethods.isEmpty()) { + for (Method annotatedMethod : annotatedMethods) { + if (InternalAnnotationUtils.isOverride(method, annotatedMethod)) { + Method resolvedSuperMethod = BridgeMethodResolver.findBridgedMethod(annotatedMethod); + result = searchWithFindSemantics(resolvedSuperMethod, annotationTypes, + annotationName, containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + // Search on interfaces declared on superclass + result = searchOnInterfaces(method, annotationTypes, annotationName, + containerType, processor, visited, metaDepth, clazz.getInterfaces()); + if (result != null) { + return result; + } + } + } + else if (element instanceof Class) { + Class clazz = (Class) element; + if (!Annotation.class.isAssignableFrom(clazz)) { + // Search on interfaces + for (Class ifc : clazz.getInterfaces()) { + T result = searchWithFindSemantics(ifc, annotationTypes, annotationName, + containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + // Search on superclass + Class superclass = clazz.getSuperclass(); + if (superclass != null && superclass != Object.class) { + T result = searchWithFindSemantics(superclass, annotationTypes, annotationName, + containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + } + catch (Throwable ex) { + InternalAnnotationUtils.handleIntrospectionFailure(element, ex); + } + } + return null; + } + + @Nullable + private static T searchOnInterfaces(Method method, Set> annotationTypes, + @Nullable String annotationName, @Nullable Class containerType, + Processor processor, Set visited, int metaDepth, Class[] ifcs) { + + for (Class ifc : ifcs) { + Set annotatedMethods = InternalAnnotationUtils.getAnnotatedMethodsInBaseType(ifc); + if (!annotatedMethods.isEmpty()) { + for (Method annotatedMethod : annotatedMethods) { + if (InternalAnnotationUtils.isOverride(method, annotatedMethod)) { + T result = searchWithFindSemantics(annotatedMethod, annotationTypes, annotationName, + containerType, processor, visited, metaDepth); + if (result != null) { + return result; + } + } + } + } + } + + return null; + } + + /** + * Get the array of raw (unsynthesized) annotations from the {@code value} + * attribute of the supplied repeatable annotation {@code container}. + * @since 4.3 + */ + @SuppressWarnings("unchecked") + private static A[] getRawAnnotationsFromContainer( + @Nullable AnnotatedElement element, Annotation container) { + + try { + A[] value = (A[]) InternalAnnotationUtils.getValue(container); + if (value != null) { + return value; + } + } + catch (Throwable ex) { + InternalAnnotationUtils.handleIntrospectionFailure(element, ex); + } + // Unable to read value from repeating annotation container -> ignore it. + return (A[]) EMPTY_ANNOTATION_ARRAY; + } + + /** + * Resolve the container type for the supplied repeatable {@code annotationType}. + *

Delegates to {@link InternalAnnotationUtils#resolveContainerAnnotationType(Class)}. + * @param annotationType the annotation type to resolve the container for + * @return the container type (never {@code null}) + * @throws IllegalArgumentException if the container type cannot be resolved + * @since 4.3 + */ + private static Class resolveContainerType(Class annotationType) { + Class containerType = InternalAnnotationUtils.resolveContainerAnnotationType(annotationType); + if (containerType == null) { + throw new IllegalArgumentException( + "Annotation type must be a repeatable annotation: failed to resolve container type for " + + annotationType.getName()); + } + return containerType; + } + + /** + * Validate that the supplied {@code containerType} is a proper container + * annotation for the supplied repeatable {@code annotationType} (i.e. + * that it declares a {@code value} attribute that holds an array of the + * {@code annotationType}). + * @throws AnnotationConfigurationException if the supplied {@code containerType} + * is not a valid container annotation for the supplied {@code annotationType} + * @since 4.3 + */ + private static void validateContainerType(Class annotationType, + Class containerType) { + + try { + Method method = containerType.getDeclaredMethod(InternalAnnotationUtils.VALUE); + Class returnType = method.getReturnType(); + if (!returnType.isArray() || returnType.getComponentType() != annotationType) { + String msg = String.format( + "Container type [%s] must declare a 'value' attribute for an array of type [%s]", + containerType.getName(), annotationType.getName()); + throw new AnnotationConfigurationException(msg); + } + } + catch (Throwable ex) { + InternalAnnotationUtils.rethrowAnnotationConfigurationException(ex); + String msg = String.format("Invalid declaration of container type [%s] for repeatable annotation [%s]", + containerType.getName(), annotationType.getName()); + throw new AnnotationConfigurationException(msg, ex); + } + } + + /** + * Post-process the aggregated results into a set of synthesized annotations. + * @param element the annotated element + * @param aggregatedResults the aggregated results for the given element + * @return the set of annotations + */ + @SuppressWarnings("unchecked") + private static Set postProcessAndSynthesizeAggregatedResults( + AnnotatedElement element, List aggregatedResults) { + + Set annotations = new LinkedHashSet<>(); + for (AnnotationAttributes attributes : aggregatedResults) { + InternalAnnotationUtils.postProcessAnnotationAttributes(element, attributes, false, false); + Class annType = attributes.annotationType(); + if (annType != null) { + annotations.add((A) InternalAnnotationUtils.synthesizeAnnotation(attributes, annType, element)); + } + } + return annotations; + } + + + /** + * Callback interface that is used to process annotations during a search. + *

Depending on the use case, a processor may choose to {@linkplain #process} + * a single target annotation, multiple target annotations, or all annotations + * discovered by the currently executing search. The term "target" in this + * context refers to a matching annotation (i.e. a specific annotation type + * that was found during the search). + *

Returning a non-null value from the {@link #process} method instructs + * the search algorithm to stop searching further; whereas, returning + * {@code null} from the {@link #process} method instructs the search + * algorithm to continue searching for additional annotations. One exception + * to this rule applies to processors that {@linkplain #aggregates aggregate} + * results. If an aggregating processor returns a non-null value, that value + * will be added to the {@linkplain #getAggregatedResults aggregated results} + * and the search algorithm will continue. + *

Processors can optionally {@linkplain #postProcess post-process} the + * result of the {@link #process} method as the search algorithm goes back + * down the annotation hierarchy from an invocation of {@link #process} that + * returned a non-null value down to the {@link AnnotatedElement} that was + * supplied as the starting point to the search algorithm. + * @param the type of result returned by the processor + */ + private interface Processor { + + /** + * Process the supplied annotation. + *

The supplied annotation will be an actual target annotation + * that has been found by the search algorithm, unless this processor + * is configured to {@linkplain #alwaysProcesses always process} + * annotations in which case it may be some other annotation within an + * annotation hierarchy. In the latter case, the {@code metaDepth} + * will have a value greater than {@code 0}. In any case, it is + * up to concrete implementations of this method to decide what to + * do with the supplied annotation. + *

The {@code metaDepth} parameter represents the depth of the + * annotation relative to the first annotated element in the + * annotation hierarchy. For example, an annotation that is + * present on a non-annotation element will have a depth + * of 0; a meta-annotation will have a depth of 1; and a + * meta-meta-annotation will have a depth of 2; etc. + * @param annotatedElement the element that is annotated with the + * supplied annotation, used for contextual logging; may be + * {@code null} if unknown + * @param annotation the annotation to process + * @param metaDepth the meta-depth of the annotation + * @return the result of the processing, or {@code null} to continue + * searching for additional annotations + */ + @Nullable + T process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth); + + /** + * Post-process the result returned by the {@link #process} method. + *

The {@code annotation} supplied to this method is an annotation + * that is present in the annotation hierarchy, between the initial + * {@link AnnotatedElement} and an invocation of {@link #process} + * that returned a non-null value. + * @param annotatedElement the element that is annotated with the + * supplied annotation, used for contextual logging; may be + * {@code null} if unknown + * @param annotation the annotation to post-process + * @param result the result to post-process + */ + void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result); + + /** + * Determine if this processor always processes annotations regardless of + * whether or not the target annotation has been found. + * @return {@code true} if this processor always processes annotations + * @since 4.3 + */ + boolean alwaysProcesses(); + + /** + * Determine if this processor aggregates the results returned by {@link #process}. + *

If this method returns {@code true}, then {@link #getAggregatedResults()} + * must return a non-null value. + * @return {@code true} if this processor supports aggregated results + * @since 4.3 + * @see #getAggregatedResults + */ + boolean aggregates(); + + /** + * Get the list of results aggregated by this processor. + *

NOTE: the processor does not aggregate the results + * itself. Rather, the search algorithm that uses this processor is + * responsible for asking this processor if it {@link #aggregates} results + * and then adding the post-processed results to the list returned by this + * method. + * @return the list of results aggregated by this processor (never {@code null}) + * @since 4.3 + * @see #aggregates + */ + List getAggregatedResults(); + } + + + /** + * {@link Processor} that {@linkplain #process(AnnotatedElement, Annotation, int) + * processes} annotations but does not {@linkplain #postProcess post-process} or + * {@linkplain #aggregates aggregate} results. + * @since 4.2 + */ + private abstract static class SimpleAnnotationProcessor implements Processor { + + private final boolean alwaysProcesses; + + public SimpleAnnotationProcessor() { + this(false); + } + + public SimpleAnnotationProcessor(boolean alwaysProcesses) { + this.alwaysProcesses = alwaysProcesses; + } + + @Override + public final boolean alwaysProcesses() { + return this.alwaysProcesses; + } + + @Override + public final void postProcess(@Nullable AnnotatedElement annotatedElement, Annotation annotation, T result) { + // no-op + } + + @Override + public final boolean aggregates() { + return false; + } + + @Override + public final List getAggregatedResults() { + throw new UnsupportedOperationException("SimpleAnnotationProcessor does not support aggregated results"); + } + } + + + /** + * {@code SimpleAnnotationProcessor} that always returns {@link Boolean#TRUE} when + * asked to {@linkplain #process(AnnotatedElement, Annotation, int) process} an + * annotation. + * @since 4.3 + */ + static class AlwaysTrueBooleanAnnotationProcessor extends SimpleAnnotationProcessor { + + @Override + public final Boolean process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { + return Boolean.TRUE; + } + } + + + /** + * {@link Processor} that gets the {@code AnnotationAttributes} for the + * target annotation during the {@link #process} phase and then merges + * annotation attributes from lower levels in the annotation hierarchy + * during the {@link #postProcess} phase. + *

A {@code MergedAnnotationAttributesProcessor} may optionally be + * configured to {@linkplain #aggregates aggregate} results. + * @since 4.2 + * @see InternalAnnotationUtils#retrieveAnnotationAttributes + * @see InternalAnnotationUtils#postProcessAnnotationAttributes + */ + private static class MergedAnnotationAttributesProcessor implements Processor { + + private final boolean classValuesAsString; + + private final boolean nestedAnnotationsAsMap; + + private final boolean aggregates; + + private final List aggregatedResults; + + MergedAnnotationAttributesProcessor() { + this(false, false, false); + } + + MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap) { + this(classValuesAsString, nestedAnnotationsAsMap, false); + } + + MergedAnnotationAttributesProcessor(boolean classValuesAsString, boolean nestedAnnotationsAsMap, + boolean aggregates) { + + this.classValuesAsString = classValuesAsString; + this.nestedAnnotationsAsMap = nestedAnnotationsAsMap; + this.aggregates = aggregates; + this.aggregatedResults = (aggregates ? new ArrayList<>() : Collections.emptyList()); + } + + @Override + public boolean alwaysProcesses() { + return false; + } + + @Override + public boolean aggregates() { + return this.aggregates; + } + + @Override + public List getAggregatedResults() { + return this.aggregatedResults; + } + + @Override + @Nullable + public AnnotationAttributes process(@Nullable AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) { + return InternalAnnotationUtils.retrieveAnnotationAttributes(annotatedElement, annotation, + this.classValuesAsString, this.nestedAnnotationsAsMap); + } + + @Override + public void postProcess(@Nullable AnnotatedElement element, Annotation annotation, AnnotationAttributes attributes) { + annotation = InternalAnnotationUtils.synthesizeAnnotation(annotation, element); + Class targetAnnotationType = attributes.annotationType(); + + // Track which attribute values have already been replaced so that we can short + // circuit the search algorithms. + Set valuesAlreadyReplaced = new HashSet<>(); + + for (Method attributeMethod : InternalAnnotationUtils.getAttributeMethods(annotation.annotationType())) { + String attributeName = attributeMethod.getName(); + String attributeOverrideName = InternalAnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType); + + // Explicit annotation attribute override declared via @AliasFor + if (attributeOverrideName != null) { + if (valuesAlreadyReplaced.contains(attributeOverrideName)) { + continue; + } + + List targetAttributeNames = new ArrayList<>(); + targetAttributeNames.add(attributeOverrideName); + valuesAlreadyReplaced.add(attributeOverrideName); + + // Ensure all aliased attributes in the target annotation are overridden. (SPR-14069) + List aliases = InternalAnnotationUtils.getAttributeAliasMap(targetAnnotationType).get(attributeOverrideName); + if (aliases != null) { + for (String alias : aliases) { + if (!valuesAlreadyReplaced.contains(alias)) { + targetAttributeNames.add(alias); + valuesAlreadyReplaced.add(alias); + } + } + } + + overrideAttributes(element, annotation, attributes, attributeName, targetAttributeNames); + } + // Implicit annotation attribute override based on convention + else if (!InternalAnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) { + overrideAttribute(element, annotation, attributes, attributeName, attributeName); + } + } + } + + private void overrideAttributes(@Nullable AnnotatedElement element, Annotation annotation, + AnnotationAttributes attributes, String sourceAttributeName, List targetAttributeNames) { + + Object adaptedValue = getAdaptedValue(element, annotation, sourceAttributeName); + + for (String targetAttributeName : targetAttributeNames) { + attributes.put(targetAttributeName, adaptedValue); + } + } + + private void overrideAttribute(@Nullable AnnotatedElement element, Annotation annotation, + AnnotationAttributes attributes, String sourceAttributeName, String targetAttributeName) { + + attributes.put(targetAttributeName, getAdaptedValue(element, annotation, sourceAttributeName)); + } + + @Nullable + private Object getAdaptedValue( + @Nullable AnnotatedElement element, Annotation annotation, String sourceAttributeName) { + + Object value = InternalAnnotationUtils.getValue(annotation, sourceAttributeName); + return InternalAnnotationUtils.adaptValue(element, value, this.classValuesAsString, this.nestedAnnotationsAsMap); + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotationUtils.java new file mode 100644 index 0000000000..8312f953e2 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotationUtils.java @@ -0,0 +1,2360 @@ +/* + * 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 + * + * http://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; + +/** + * Internal class containing the version of {@link AnnotationUtils} shipped with + * Spring Framework 5.1. + * + * @author Rob Harrop + * @author Juergen Hoeller + * @author Sam Brannen + * @author Mark Fisher + * @author Chris Beams + * @author Phillip Webb + * @author Oleg Zhurakousky + * @since 5.1 + * @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() + */ +abstract class InternalAnnotationUtils { + + /** + * 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, Class annotationType) { + // 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, 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 annotations on, or {@code null} + * @return the first {@link Class} in the inheritance hierarchy that + * declares an annotation of at least one of the specified + * {@code annotationTypes}, or {@code null} if not found + * @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) { + if (annotation == null) { + return null; + } + return getDefaultValue(annotation.annotationType(), attributeName); + } + + /** + * 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(InternalAnnotationUtils.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[]) InternalAnnotationUtils.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); + } + } + + + /** + * Internal holder used to wrap default values. + */ + static class DefaultValueHolder { + + final Object defaultValue; + + public DefaultValueHolder(Object defaultValue) { + this.defaultValue = defaultValue; + } + + @Override + public String toString() { + return "*" + this.defaultValue; + } + } + +} diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java index 4ef4abd7b1..b9e7139b0d 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * 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. @@ -91,9 +91,9 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib Map originalAttributes, Class annotationType) { Map attributes = new LinkedHashMap<>(originalAttributes); - Map> attributeAliasMap = AnnotationUtils.getAttributeAliasMap(annotationType); + Map> attributeAliasMap = InternalAnnotationUtils.getAttributeAliasMap(annotationType); - for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType)) { + for (Method attributeMethod : InternalAnnotationUtils.getAttributeMethods(annotationType)) { String attributeName = attributeMethod.getName(); Object attributeValue = attributes.get(attributeName); @@ -158,7 +158,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib Class nestedAnnotationType = (Class) requiredReturnType.getComponentType(); Map[] maps = (Map[]) attributeValue; - attributes.put(attributeName, AnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType)); + attributes.put(attributeName, InternalAnnotationUtils.synthesizeAnnotationArray(maps, nestedAnnotationType)); converted = true; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java index 74d928b13c..41695e400f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizedAnnotationInvocationHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -70,10 +70,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { if (ReflectionUtils.isToStringMethod(method)) { return annotationToString(); } - if (AnnotationUtils.isAnnotationTypeMethod(method)) { + if (InternalAnnotationUtils.isAnnotationTypeMethod(method)) { return annotationType(); } - if (!AnnotationUtils.isAttributeMethod(method)) { + if (!InternalAnnotationUtils.isAttributeMethod(method)) { throw new AnnotationConfigurationException(String.format( "Method [%s] is unsupported for synthesized annotation type [%s]", method, annotationType())); } @@ -97,10 +97,10 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { // Synthesize nested annotations before returning them. if (value instanceof Annotation) { - value = AnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement()); + value = InternalAnnotationUtils.synthesizeAnnotation((Annotation) value, this.attributeExtractor.getAnnotatedElement()); } else if (value instanceof Annotation[]) { - value = AnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement()); + value = InternalAnnotationUtils.synthesizeAnnotationArray((Annotation[]) value, this.attributeExtractor.getAnnotatedElement()); } this.valueCache.put(attributeName, value); @@ -161,7 +161,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { return false; } - for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) { + for (Method attributeMethod : InternalAnnotationUtils.getAttributeMethods(annotationType())) { Object thisValue = getAttributeValue(attributeMethod); Object otherValue = ReflectionUtils.invokeMethod(attributeMethod, other); if (!ObjectUtils.nullSafeEquals(thisValue, otherValue)) { @@ -178,7 +178,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { private int annotationHashCode() { int result = 0; - for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotationType())) { + for (Method attributeMethod : InternalAnnotationUtils.getAttributeMethods(annotationType())) { Object value = getAttributeValue(attributeMethod); int hashCode; if (value.getClass().isArray()) { @@ -236,7 +236,7 @@ class SynthesizedAnnotationInvocationHandler implements InvocationHandler { private String annotationToString() { StringBuilder sb = new StringBuilder("@").append(annotationType().getName()).append("("); - Iterator iterator = AnnotationUtils.getAttributeMethods(annotationType()).iterator(); + Iterator iterator = InternalAnnotationUtils.getAttributeMethods(annotationType()).iterator(); while (iterator.hasNext()) { Method attributeMethod = iterator.next(); sb.append(attributeMethod.getName()); diff --git a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java index 9f76491246..56f720db0a 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/SynthesizingMethodParameter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * 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. @@ -104,7 +104,6 @@ public class SynthesizingMethodParameter extends MethodParameter { return AnnotationUtils.synthesizeAnnotationArray(annotations, getAnnotatedElement()); } - @Override public SynthesizingMethodParameter clone() { return new SynthesizingMethodParameter(this); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 87fc87f9b2..6c50a35d44 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -732,19 +732,19 @@ public class AnnotationUtilsTests { public void getAttributeOverrideNameFromWrongTargetAnnotation() throws Exception { Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile"); assertThat("xmlConfigFile is not an alias for @Component.", - getAttributeOverrideName(attribute, Component.class), is(nullValue())); + InternalAnnotationUtils.getAttributeOverrideName(attribute, Component.class), is(nullValue())); } @Test public void getAttributeOverrideNameForNonAliasedAttribute() throws Exception { Method nonAliasedAttribute = ImplicitAliasesContextConfig.class.getDeclaredMethod("nonAliasedAttribute"); - assertThat(getAttributeOverrideName(nonAliasedAttribute, ContextConfig.class), is(nullValue())); + assertThat(InternalAnnotationUtils.getAttributeOverrideName(nonAliasedAttribute, ContextConfig.class), is(nullValue())); } @Test public void getAttributeOverrideNameFromAliasedComposedAnnotation() throws Exception { Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile"); - assertEquals("location", getAttributeOverrideName(attribute, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(attribute, ContextConfig.class)); } @Test @@ -757,17 +757,17 @@ public class AnnotationUtilsTests { Method location3 = ImplicitAliasesContextConfig.class.getDeclaredMethod("location3"); // Meta-annotation attribute overrides - assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(groovyScript, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(value, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(xmlFile, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(groovyScript, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(value, ContextConfig.class)); // Implicit aliases - assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "groovyScript", "location1", "location2", "location3")); - assertThat(getAttributeAliasNames(groovyScript), containsInAnyOrder("value", "xmlFile", "location1", "location2", "location3")); - assertThat(getAttributeAliasNames(value), containsInAnyOrder("xmlFile", "groovyScript", "location1", "location2", "location3")); - assertThat(getAttributeAliasNames(location1), containsInAnyOrder("xmlFile", "groovyScript", "value", "location2", "location3")); - assertThat(getAttributeAliasNames(location2), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location3")); - assertThat(getAttributeAliasNames(location3), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location2")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "groovyScript", "location1", "location2", "location3")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(groovyScript), containsInAnyOrder("value", "xmlFile", "location1", "location2", "location3")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(value), containsInAnyOrder("xmlFile", "groovyScript", "location1", "location2", "location3")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(location1), containsInAnyOrder("xmlFile", "groovyScript", "value", "location2", "location3")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(location2), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location3")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(location3), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location2")); } @Test @@ -776,12 +776,12 @@ public class AnnotationUtilsTests { Method groovyScript = ImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("groovyScript"); // Meta-annotation attribute overrides - assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class)); - assertEquals("value", getAttributeOverrideName(groovyScript, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(xmlFile, ContextConfig.class)); + assertEquals("value", InternalAnnotationUtils.getAttributeOverrideName(groovyScript, ContextConfig.class)); // Implicit aliases - assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("groovyScript")); - assertThat(getAttributeAliasNames(groovyScript), containsInAnyOrder("xmlFile")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xmlFile), containsInAnyOrder("groovyScript")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(groovyScript), containsInAnyOrder("xmlFile")); } @Test @@ -793,14 +793,14 @@ public class AnnotationUtilsTests { Method xmlFile = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xmlFile"); // Meta-annotation attribute overrides - assertEquals("value", getAttributeOverrideName(value, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(location, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class)); + assertEquals("value", InternalAnnotationUtils.getAttributeOverrideName(value, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(location, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(xmlFile, ContextConfig.class)); // Implicit aliases - assertThat(getAttributeAliasNames(value), containsInAnyOrder("location", "xmlFile")); - assertThat(getAttributeAliasNames(location), containsInAnyOrder("value", "xmlFile")); - assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "location")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(value), containsInAnyOrder("location", "xmlFile")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(location), containsInAnyOrder("value", "xmlFile")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "location")); } @Test @@ -809,16 +809,16 @@ public class AnnotationUtilsTests { Method groovy = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("groovy"); // Explicit meta-annotation attribute overrides - assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesContextConfig.class)); - assertEquals("groovyScript", getAttributeOverrideName(groovy, ImplicitAliasesContextConfig.class)); + assertEquals("xmlFile", InternalAnnotationUtils.getAttributeOverrideName(xml, ImplicitAliasesContextConfig.class)); + assertEquals("groovyScript", InternalAnnotationUtils.getAttributeOverrideName(groovy, ImplicitAliasesContextConfig.class)); // Transitive meta-annotation attribute overrides - assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(xml, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(groovy, ContextConfig.class)); // Transitive implicit aliases - assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy")); - assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xml), containsInAnyOrder("groovy")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(groovy), containsInAnyOrder("xml")); } @Test @@ -827,12 +827,12 @@ public class AnnotationUtilsTests { Method groovy = TransitiveImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("groovy"); // Explicit meta-annotation attribute overrides - assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesForAliasPairContextConfig.class)); - assertEquals("groovyScript", getAttributeOverrideName(groovy, ImplicitAliasesForAliasPairContextConfig.class)); + assertEquals("xmlFile", InternalAnnotationUtils.getAttributeOverrideName(xml, ImplicitAliasesForAliasPairContextConfig.class)); + assertEquals("groovyScript", InternalAnnotationUtils.getAttributeOverrideName(groovy, ImplicitAliasesForAliasPairContextConfig.class)); // Transitive implicit aliases - assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy")); - assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xml), containsInAnyOrder("groovy")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(groovy), containsInAnyOrder("xml")); } @Test @@ -843,23 +843,23 @@ public class AnnotationUtilsTests { Method groovy = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("groovy"); // Meta-annotation attribute overrides - assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class)); - assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(xml, ContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(groovy, ContextConfig.class)); // Explicit meta-annotation attribute overrides - assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); - assertEquals("location", getAttributeOverrideName(groovy, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); + assertEquals("xmlFile", InternalAnnotationUtils.getAttributeOverrideName(xml, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); + assertEquals("location", InternalAnnotationUtils.getAttributeOverrideName(groovy, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class)); // Transitive implicit aliases - assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml")); - assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(groovy), containsInAnyOrder("xml")); + assertThat(InternalAnnotationUtils.getAttributeAliasNames(xml), containsInAnyOrder("groovy")); } @Test public void synthesizeAnnotationWithoutAttributeAliases() throws Exception { Component component = WebController.class.getAnnotation(Component.class); assertNotNull(component); - Component synthesizedComponent = synthesizeAnnotation(component); + Component synthesizedComponent = InternalAnnotationUtils.synthesizeAnnotation(component); assertNotNull(synthesizedComponent); assertSame(component, synthesizedComponent); assertEquals("value attribute: ", "webController", synthesizedComponent.value()); @@ -870,9 +870,9 @@ public class AnnotationUtilsTests { Method method = WebController.class.getMethod("handleMappedWithValueAttribute"); WebMapping webMapping = method.getAnnotation(WebMapping.class); assertNotNull(webMapping); - WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping); + WebMapping synthesizedWebMapping = InternalAnnotationUtils.synthesizeAnnotation(webMapping); assertNotSame(webMapping, synthesizedWebMapping); - WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping); + WebMapping synthesizedAgainWebMapping = InternalAnnotationUtils.synthesizeAnnotation(synthesizedWebMapping); assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class)); assertSame(synthesizedWebMapping, synthesizedAgainWebMapping); @@ -888,7 +888,7 @@ public class AnnotationUtilsTests { exception.expectMessage(startsWith("@AliasFor declaration on attribute 'foo' in annotation")); exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName())); exception.expectMessage(containsString("points to itself")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -898,7 +898,7 @@ public class AnnotationUtilsTests { exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation")); exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName())); exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -908,7 +908,7 @@ public class AnnotationUtilsTests { exception.expectMessage(startsWith("Attribute 'foo' in")); exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName())); exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -919,7 +919,7 @@ public class AnnotationUtilsTests { exception.expectMessage(startsWith("Attribute 'bar' in")); exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName())); exception.expectMessage(containsString("@AliasFor [foo]")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -931,7 +931,7 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName())); exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")). or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'"))); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -944,7 +944,7 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString("attribute 'foo'")); exception.expectMessage(containsString("attribute 'bar'")); exception.expectMessage(containsString("same return type")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -957,7 +957,7 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString("attribute 'foo' in annotation")); exception.expectMessage(containsString("attribute 'bar' in annotation")); exception.expectMessage(containsString("default values")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -970,7 +970,7 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString("attribute 'foo' in annotation")); exception.expectMessage(containsString("attribute 'bar' in annotation")); exception.expectMessage(containsString("same default value")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -983,7 +983,7 @@ public class AnnotationUtilsTests { exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation")); exception.expectMessage(containsString(ContextConfig.class.getName())); exception.expectMessage(containsString("not meta-present")); - synthesizeAnnotation(annotation); + InternalAnnotationUtils.synthesizeAnnotation(annotation); } @Test @@ -992,7 +992,7 @@ public class AnnotationUtilsTests { WebMapping webMapping = method.getAnnotation(WebMapping.class); assertNotNull(webMapping); - WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMapping); + WebMapping synthesizedWebMapping1 = InternalAnnotationUtils.synthesizeAnnotation(webMapping); assertThat(synthesizedWebMapping1, instanceOf(SynthesizedAnnotation.class)); assertNotSame(webMapping, synthesizedWebMapping1); @@ -1000,7 +1000,7 @@ public class AnnotationUtilsTests { assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping1.path()); assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping1.value()); - WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMapping); + WebMapping synthesizedWebMapping2 = InternalAnnotationUtils.synthesizeAnnotation(webMapping); assertThat(synthesizedWebMapping2, instanceOf(SynthesizedAnnotation.class)); assertNotSame(webMapping, synthesizedWebMapping2); @@ -1021,7 +1021,7 @@ public class AnnotationUtilsTests { ImplicitAliasesContextConfig config = clazz.getAnnotation(ImplicitAliasesContextConfig.class); assertNotNull(config); - ImplicitAliasesContextConfig synthesizedConfig = synthesizeAnnotation(config); + ImplicitAliasesContextConfig synthesizedConfig = InternalAnnotationUtils.synthesizeAnnotation(config); assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); assertEquals("value: ", expected, synthesizedConfig.value()); @@ -1047,7 +1047,7 @@ public class AnnotationUtilsTests { ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class); assertNotNull(config); - ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = synthesizeAnnotation(config); + ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = InternalAnnotationUtils.synthesizeAnnotation(config); assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); assertEquals("value: ", expected, synthesizedConfig.value()); @@ -1061,7 +1061,7 @@ public class AnnotationUtilsTests { ImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(ImplicitAliasesForAliasPairContextConfig.class); assertNotNull(config); - ImplicitAliasesForAliasPairContextConfig synthesizedConfig = synthesizeAnnotation(config); + ImplicitAliasesForAliasPairContextConfig synthesizedConfig = InternalAnnotationUtils.synthesizeAnnotation(config); assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); assertEquals("xmlFile: ", "test.xml", synthesizedConfig.xmlFile()); @@ -1074,7 +1074,7 @@ public class AnnotationUtilsTests { TransitiveImplicitAliasesContextConfig config = clazz.getAnnotation(TransitiveImplicitAliasesContextConfig.class); assertNotNull(config); - TransitiveImplicitAliasesContextConfig synthesizedConfig = synthesizeAnnotation(config); + TransitiveImplicitAliasesContextConfig synthesizedConfig = InternalAnnotationUtils.synthesizeAnnotation(config); assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); assertEquals("xml: ", "test.xml", synthesizedConfig.xml()); @@ -1087,7 +1087,7 @@ public class AnnotationUtilsTests { TransitiveImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(TransitiveImplicitAliasesForAliasPairContextConfig.class); assertNotNull(config); - TransitiveImplicitAliasesForAliasPairContextConfig synthesizedConfig = synthesizeAnnotation(config); + TransitiveImplicitAliasesForAliasPairContextConfig synthesizedConfig = InternalAnnotationUtils.synthesizeAnnotation(config); assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class)); assertEquals("xml: ", "test.xml", synthesizedConfig.xml()); @@ -1241,7 +1241,7 @@ public class AnnotationUtilsTests { @Test public void synthesizeAnnotationWithAttributeAliasesWithDifferentValues() throws Exception { - ContextConfig contextConfig = synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class)); + ContextConfig contextConfig = InternalAnnotationUtils.synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class)); exception.expect(AnnotationConfigurationException.class); getValue(contextConfig); } @@ -1357,9 +1357,9 @@ public class AnnotationUtilsTests { WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class); assertNotNull(webMappingWithPathAndValue); - WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping1 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping1); - WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping2 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping2); assertThat(webMappingWithAliases.toString(), is(not(synthesizedWebMapping1.toString()))); @@ -1388,9 +1388,9 @@ public class AnnotationUtilsTests { WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class); assertNotNull(webMappingWithPathAndValue); - WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping1 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping1); - WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping2 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping2); // Equality amongst standard annotations @@ -1426,9 +1426,9 @@ public class AnnotationUtilsTests { WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class); assertNotNull(webMappingWithPathAndValue); - WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping1 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping1); - WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases); + WebMapping synthesizedWebMapping2 = InternalAnnotationUtils.synthesizeAnnotation(webMappingWithAliases); assertNotNull(synthesizedWebMapping2); // Equality amongst standard annotations @@ -1470,7 +1470,7 @@ public class AnnotationUtilsTests { Annotation annotation = clazz.getAnnotation(annotationType); assertNotNull(annotation); - Annotation synthesizedAnnotation = synthesizeAnnotation(annotation); + Annotation synthesizedAnnotation = InternalAnnotationUtils.synthesizeAnnotation(annotation); assertNotSame(annotation, synthesizedAnnotation); assertNotNull(synthesizedAnnotation); @@ -1485,7 +1485,7 @@ public class AnnotationUtilsTests { Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class); assertNotNull(hierarchy); - Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); + Hierarchy synthesizedHierarchy = InternalAnnotationUtils.synthesizeAnnotation(hierarchy); assertNotSame(hierarchy, synthesizedHierarchy); assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class)); @@ -1507,7 +1507,7 @@ public class AnnotationUtilsTests { Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class); assertNotNull(hierarchy); - Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy); + Hierarchy synthesizedHierarchy = InternalAnnotationUtils.synthesizeAnnotation(hierarchy); assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class)); ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class); @@ -1530,7 +1530,7 @@ public class AnnotationUtilsTests { public void synthesizeAnnotationWithArrayOfChars() throws Exception { CharsContainer charsContainer = GroupOfCharsClass.class.getAnnotation(CharsContainer.class); assertNotNull(charsContainer); - CharsContainer synthesizedCharsContainer = synthesizeAnnotation(charsContainer); + CharsContainer synthesizedCharsContainer = InternalAnnotationUtils.synthesizeAnnotation(charsContainer); assertThat(synthesizedCharsContainer, instanceOf(SynthesizedAnnotation.class)); char[] chars = synthesizedCharsContainer.chars(); @@ -1546,9 +1546,9 @@ public class AnnotationUtilsTests { @Test public void interfaceWithAnnotatedMethods() { - assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NonAnnotatedInterface.class).isEmpty()); - assertFalse(AnnotationUtils.getAnnotatedMethodsInBaseType(AnnotatedInterface.class).isEmpty()); - assertTrue(AnnotationUtils.getAnnotatedMethodsInBaseType(NullableAnnotatedInterface.class).isEmpty()); + assertTrue(InternalAnnotationUtils.getAnnotatedMethodsInBaseType(NonAnnotatedInterface.class).isEmpty()); + assertFalse(InternalAnnotationUtils.getAnnotatedMethodsInBaseType(AnnotatedInterface.class).isEmpty()); + assertTrue(InternalAnnotationUtils.getAnnotatedMethodsInBaseType(NullableAnnotatedInterface.class).isEmpty()); } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java index 3fa9039637..382235f40f 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MapAnnotationAttributeExtractorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * 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. @@ -122,7 +122,7 @@ public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnno // we have to rig the attributeAliasesCache in AnnotationUtils so that the tests // consistently fail in case enrichAndValidateAttributes() is buggy. // Otherwise, these tests would intermittently pass even for an invalid implementation. - Field cacheField = AnnotationUtils.class.getDeclaredField("attributeAliasesCache"); + Field cacheField = InternalAnnotationUtils.class.getDeclaredField("attributeAliasesCache"); cacheField.setAccessible(true); Map, MultiValueMap> attributeAliasesCache = (Map, MultiValueMap>) cacheField.get(null);