From b91ccf038f309dfd1f91101133598b79c0b7efdf Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 21 Nov 2018 21:35:48 -0800 Subject: [PATCH] Start migration annotation utility methods Create internal variants of the existing `AnnotationUtils` and `AnnotatedElementUtils` classes and migrate the existing classes to use them. The internal variants will be used to check that the same results are given as we migrate the utils methods to use the new `MergedAnnotations` API. See gh-22562 --- ...liasAwareAnnotationAttributeExtractor.java | 4 +- .../annotation/AnnotatedElementUtils.java | 1048 +------- .../core/annotation/AnnotationUtils.java | 1634 +----------- .../InternalAnnotatedElementUtils.java | 1596 +++++++++++ .../annotation/InternalAnnotationUtils.java | 2360 +++++++++++++++++ .../MapAnnotationAttributeExtractor.java | 8 +- ...ynthesizedAnnotationInvocationHandler.java | 16 +- .../SynthesizingMethodParameter.java | 3 +- .../core/annotation/AnnotationUtilsTests.java | 144 +- .../MapAnnotationAttributeExtractorTests.java | 4 +- 10 files changed, 4141 insertions(+), 2676 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotatedElementUtils.java create mode 100644 spring-core/src/main/java/org/springframework/core/annotation/InternalAnnotationUtils.java 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);