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

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

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 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;

/**
 * General utility methods for finding annotations, meta-annotations, and
 * repeatable annotations on {@link AnnotatedElement AnnotatedElements}.
 *
 * 

{@code AnnotatedElementUtils} defines the public API for Spring's * meta-annotation programming model with support for annotation attribute * overrides. If you do not need support for annotation attribute * overrides, consider using {@link AnnotationUtils} instead. * *

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

Annotation Attribute Overrides

*

Support for meta-annotations with attribute overrides in * composed annotations is provided by all variants of the * {@code getMergedAnnotationAttributes()}, {@code getMergedAnnotation()}, * {@code getAllMergedAnnotations()}, {@code getMergedRepeatableAnnotations()}, * {@code findMergedAnnotationAttributes()}, {@code findMergedAnnotation()}, * {@code findAllMergedAnnotations()}, and {@code findMergedRepeatableAnnotations()} * methods. * *

Find vs. Get Semantics

*

The search algorithms used by methods in this class follow either * find or get semantics. Consult the javadocs for each * individual method for details on which search algorithm is used. * *

Get semantics are limited to searching for annotations * that are either present on an {@code AnnotatedElement} (i.e. declared * locally or {@linkplain java.lang.annotation.Inherited inherited}) or declared * within the annotation hierarchy above the {@code AnnotatedElement}. * *

Find semantics are much more exhaustive, providing * get semantics plus support for the following: * *

    *
  • Searching on interfaces, if the annotated element is a class *
  • Searching on superclasses, if the annotated element is a class *
  • Resolving bridged methods, if the annotated element is a method *
  • Searching on methods in interfaces, if the annotated element is a method *
  • Searching on methods in superclasses, if the annotated element is a method *
* *

Support for {@code @Inherited}

*

Methods following get semantics will honor the contract of Java's * {@link java.lang.annotation.Inherited @Inherited} annotation except that locally * declared annotations (including custom composed annotations) will be favored over * inherited annotations. In contrast, methods following find semantics * will completely ignore the presence of {@code @Inherited} since the find * search algorithm manually traverses type and method hierarchies and thereby * implicitly supports annotation inheritance without a need for {@code @Inherited}. * * @author Phillip Webb * @author Juergen Hoeller * @author Sam Brannen * @since 4.0 * @see AliasFor * @see AnnotationAttributes * @see AnnotationUtils * @see BridgeMethodResolver */ 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}. * @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 AnnotatedElementUtils 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 AnnotatedElementUtils 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, 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); } } /** * 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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()); AnnotationUtils.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 AnnotatedElementUtils 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)); AnnotationUtils.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 AnnotationUtils#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 AnnotationUtils#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 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); } /** * 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 = AnnotationUtils.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 AnnotatedElementUtils 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 AnnotatedElementUtils 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)); AnnotationUtils.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 AnnotatedElementUtils 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)); AnnotationUtils.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 AnnotatedElementUtils 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 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); } /** * 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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 AnnotatedElementUtils 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) { 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); } } }