org.springframework.core.annotation.AnnotatedElementUtils Maven / Gradle / Ivy
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.annotation.MergedAnnotation.Adapt;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.lang.Nullable;
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 {
/**
* 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(Annotation... annotations) {
return new AnnotatedElementForAnnotations(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) {
for (Annotation annotation : element.getAnnotations()) {
if (annotation.annotationType().getName().equals(annotationName)) {
return getMetaAnnotationTypes(element, annotation);
}
}
return Collections.emptySet();
}
private static Set getMetaAnnotationTypes(AnnotatedElement element, @Nullable Annotation annotation) {
if (annotation == null) {
return Collections.emptySet();
}
return getAnnotations(annotation.annotationType()).stream()
.map(mergedAnnotation -> mergedAnnotation.getType().getName())
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* 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 getAnnotations(element).stream(annotationType).anyMatch(MergedAnnotation::isMetaPresent);
}
/**
* 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 getAnnotations(element).stream(annotationName).anyMatch(MergedAnnotation::isMetaPresent);
}
/**
* 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 merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return element.isAnnotationPresent(annotationType);
}
// Exhaustive retrieval of merged annotations...
return getAnnotations(element).isPresent(annotationType);
}
/**
* 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 getAnnotations(element).isPresent(annotationName);
}
/**
* 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) {
MergedAnnotation mergedAnnotation = getAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared());
return getAnnotationAttributes(mergedAnnotation, 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.
*
{@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) {
MergedAnnotation mergedAnnotation = getAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap);
}
/**
* 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.
* @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 #findMergedAnnotation(AnnotatedElement, Class)
*/
@Nullable
public static A getMergedAnnotation(AnnotatedElement element, Class annotationType) {
// Shortcut: directly present on the element, with no merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return element.getDeclaredAnnotation(annotationType);
}
// Exhaustive retrieval of merged annotations...
return getAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(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) {
return getAnnotations(element).stream(annotationType)
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
/**
* 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) {
return getAnnotations(element).stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes))
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
/**
* 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) {
return getRepeatableAnnotations(element, containerType, annotationType)
.stream(annotationType)
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
/**
* 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) {
Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap);
return getAnnotations(element).stream(annotationName)
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getMetaTypes))
.map(MergedAnnotation::withNonMergedAttributes)
.collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, adaptations));
}
/**
* 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 merging needed?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return element.isAnnotationPresent(annotationType);
}
// Exhaustive retrieval of merged annotations...
return findAnnotations(element).isPresent(annotationType);
}
/**
* 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) {
MergedAnnotation mergedAnnotation = findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared());
return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap);
}
/**
* 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) {
MergedAnnotation mergedAnnotation = findAnnotations(element)
.get(annotationName, null, MergedAnnotationSelectors.firstDirectlyDeclared());
return getAnnotationAttributes(mergedAnnotation, classValuesAsString, nestedAnnotationsAsMap);
}
/**
* 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?
if (AnnotationFilter.PLAIN.matches(annotationType) ||
AnnotationsScanner.hasPlainJavaAnnotationsOnly(element)) {
return element.getDeclaredAnnotation(annotationType);
}
// Exhaustive retrieval of merged annotations...
return findAnnotations(element)
.get(annotationType, null, MergedAnnotationSelectors.firstDirectlyDeclared())
.synthesize(MergedAnnotation::isPresent).orElse(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) {
return findAnnotations(element).stream(annotationType)
.sorted(highAggregateIndexesFirst())
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
/**
* 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) {
return findAnnotations(element).stream()
.filter(MergedAnnotationPredicates.typeIn(annotationTypes))
.sorted(highAggregateIndexesFirst())
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
/**
* 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) {
return findRepeatableAnnotations(element, containerType, annotationType)
.stream(annotationType)
.sorted(highAggregateIndexesFirst())
.collect(MergedAnnotationCollectors.toAnnotationSet());
}
private static MergedAnnotations getAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none());
}
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
@Nullable Class containerType, Class annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, repeatableContainers);
}
private static MergedAnnotations findAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
}
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
@Nullable Class containerType, Class annotationType) {
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
}
@Nullable
private static MultiValueMap nullIfEmpty(MultiValueMap map) {
return (map.isEmpty() ? null : map);
}
private static Comparator> highAggregateIndexesFirst() {
return Comparator.> comparingInt(
MergedAnnotation::getAggregateIndex).reversed();
}
@Nullable
private static AnnotationAttributes getAnnotationAttributes(MergedAnnotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
if (!annotation.isPresent()) {
return null;
}
return annotation.asAnnotationAttributes(
Adapt.values(classValuesAsString, nestedAnnotationsAsMap));
}
/**
* Adapted {@link AnnotatedElement} that hold specific annotations.
*/
private static class AnnotatedElementForAnnotations implements AnnotatedElement {
private final Annotation[] annotations;
AnnotatedElementForAnnotations(Annotation... annotations) {
this.annotations = annotations;
}
@Override
@SuppressWarnings("unchecked")
@Nullable
public T getAnnotation(Class annotationClass) {
for (Annotation annotation : this.annotations) {
if (annotation.annotationType() == annotationClass) {
return (T) annotation;
}
}
return null;
}
@Override
public Annotation[] getAnnotations() {
return this.annotations.clone();
}
@Override
public Annotation[] getDeclaredAnnotations() {
return this.annotations.clone();
}
}
}