Skip to content

Commit 46e0484

Browse files
committed
Support searches for merged repeatable annotations with "get" semantics
This commit picks up where 2535469 left off with added support for "get" search semantics for merged repeatable annotations. Specifically, this commit introduces a new getMergedRepeatableAnnotations() method in AnnotatedElementUtils. Issue: SPR-13973
1 parent 8d0083c commit 46e0484

File tree

4 files changed

+325
-46
lines changed

4 files changed

+325
-46
lines changed

spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java

Lines changed: 145 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
* <p>Support for meta-annotations with <em>attribute overrides</em> in
4949
* <em>composed annotations</em> is provided by all variants of the
5050
* {@code getMergedAnnotationAttributes()}, {@code getMergedAnnotation()},
51+
* {@code getAllMergedAnnotations()}, {@code getMergedRepeatableAnnotations()},
5152
* {@code findMergedAnnotationAttributes()}, {@code findMergedAnnotation()},
5253
* {@code findAllMergedAnnotations()}, and {@code findMergedRepeatableAnnotations()}
5354
* methods.
@@ -150,7 +151,8 @@ public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Class
150151
try {
151152
Annotation annotation = element.getAnnotation(annotationType);
152153
if (annotation != null) {
153-
searchWithGetSemantics(annotation.annotationType(), annotationType, null, new SimpleAnnotationProcessor<Object>() {
154+
searchWithGetSemantics(annotation.annotationType(), annotationType, null, null,
155+
new SimpleAnnotationProcessor<Object>() {
154156
@Override
155157
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
156158
types.add(annotation.annotationType().getName());
@@ -189,7 +191,8 @@ public static Set<String> getMetaAnnotationTypes(AnnotatedElement element, Strin
189191
try {
190192
Annotation annotation = AnnotationUtils.getAnnotation(element, annotationName);
191193
if (annotation != null) {
192-
searchWithGetSemantics(annotation.annotationType(), null, annotationName, new SimpleAnnotationProcessor<Object>() {
194+
searchWithGetSemantics(annotation.annotationType(), null, annotationName, null,
195+
new SimpleAnnotationProcessor<Object>() {
193196
@Override
194197
public Object process(AnnotatedElement annotatedElement, Annotation annotation, int metaDepth) {
195198
types.add(annotation.annotationType().getName());
@@ -409,6 +412,7 @@ public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElemen
409412
public static AnnotationAttributes getMergedAnnotationAttributes(AnnotatedElement element,
410413
String annotationName, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
411414

415+
Assert.hasLength(annotationName, "annotationName must not be null or empty");
412416
AnnotationAttributes attributes = searchWithGetSemantics(element, null, annotationName,
413417
new MergedAnnotationAttributesProcessor(null, annotationName, classValuesAsString, nestedAnnotationsAsMap));
414418
AnnotationUtils.postProcessAnnotationAttributes(element, attributes, classValuesAsString, nestedAnnotationsAsMap);
@@ -483,6 +487,81 @@ public static <A extends Annotation> Set<A> getAllMergedAnnotations(AnnotatedEle
483487
return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
484488
}
485489

490+
/**
491+
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
492+
* within the annotation hierarchy <em>above</em> the supplied {@code element};
493+
* and for each annotation found, merge that annotation's attributes with
494+
* <em>matching</em> attributes from annotations in lower levels of the annotation
495+
* hierarchy and synthesize the results back into an annotation of the specified
496+
* {@code annotationType}.
497+
* <p>The container type that holds the repeatable annotations will be looked up
498+
* via {@link java.lang.annotation.Repeatable}.
499+
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
500+
* single annotation and within annotation hierarchies.
501+
* <p>This method follows <em>get semantics</em> as described in the
502+
* {@linkplain AnnotatedElementUtils class-level javadoc}.
503+
* @param element the annotated element; never {@code null}
504+
* @param annotationType the annotation type to find; never {@code null}
505+
* @return the set of all merged repeatable {@code Annotations} found, or an empty
506+
* set if none were found
507+
* @since 4.3
508+
* @see #getMergedAnnotation(AnnotatedElement, Class)
509+
* @see #getAllMergedAnnotations(AnnotatedElement, Class)
510+
* @see #getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
511+
* @throws IllegalArgumentException if the {@code element} or {@code annotationType}
512+
* is {@code null}, or if the container type cannot be resolved
513+
*/
514+
public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
515+
Class<A> annotationType) {
516+
517+
return getMergedRepeatableAnnotations(element, annotationType, null);
518+
}
519+
520+
/**
521+
* Get all <em>repeatable annotations</em> of the specified {@code annotationType}
522+
* within the annotation hierarchy <em>above</em> the supplied {@code element};
523+
* and for each annotation found, merge that annotation's attributes with
524+
* <em>matching</em> attributes from annotations in lower levels of the annotation
525+
* hierarchy and synthesize the results back into an annotation of the specified
526+
* {@code annotationType}.
527+
* <p>{@link AliasFor @AliasFor} semantics are fully supported, both within a
528+
* single annotation and within annotation hierarchies.
529+
* <p>This method follows <em>get semantics</em> as described in the
530+
* {@linkplain AnnotatedElementUtils class-level javadoc}.
531+
* @param element the annotated element; never {@code null}
532+
* @param annotationType the annotation type to find; never {@code null}
533+
* @param containerType the type of the container that holds the annotations;
534+
* may be {@code null} if the container type should be looked up via
535+
* {@link java.lang.annotation.Repeatable}
536+
* @return the set of all merged repeatable {@code Annotations} found, or an empty
537+
* set if none were found
538+
* @since 4.3
539+
* @see #getMergedAnnotation(AnnotatedElement, Class)
540+
* @see #getAllMergedAnnotations(AnnotatedElement, Class)
541+
* @throws IllegalArgumentException if the {@code element} or {@code annotationType}
542+
* is {@code null}, or if the container type cannot be resolved
543+
* @throws AnnotationConfigurationException if the supplied {@code containerType}
544+
* is not a valid container annotation for the supplied {@code annotationType}
545+
*/
546+
public static <A extends Annotation> Set<A> getMergedRepeatableAnnotations(AnnotatedElement element,
547+
Class<A> annotationType, Class<? extends Annotation> containerType) {
548+
549+
Assert.notNull(element, "AnnotatedElement must not be null");
550+
Assert.notNull(annotationType, "annotationType must not be null");
551+
552+
if (containerType == null) {
553+
containerType = resolveContainerType(annotationType);
554+
}
555+
else {
556+
validateRepeatableContainerType(annotationType, containerType);
557+
}
558+
559+
MergedAnnotationAttributesProcessor processor =
560+
new MergedAnnotationAttributesProcessor(annotationType, null, false, false, true);
561+
searchWithGetSemantics(element, annotationType, null, containerType, processor);
562+
return postProcessAndSynthesizeAggregatedResults(element, annotationType, processor.getAggregatedResults());
563+
}
564+
486565
/**
487566
* Get the annotation attributes of <strong>all</strong> annotations of the specified
488567
* {@code annotationName} in the annotation hierarchy above the supplied
@@ -832,12 +911,32 @@ public static <A extends Annotation> Set<A> findMergedRepeatableAnnotations(Anno
832911
* @param processor the processor to delegate to
833912
* @return the result of the processor, potentially {@code null}
834913
*/
835-
private static <T> T searchWithGetSemantics(AnnotatedElement element,
836-
Class<? extends Annotation> annotationType, String annotationName, Processor<T> processor) {
914+
private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
915+
String annotationName, Processor<T> processor) {
916+
917+
return searchWithGetSemantics(element, annotationType, annotationName, null, processor);
918+
}
919+
920+
/**
921+
* Search for annotations of the specified {@code annotationName} or
922+
* {@code annotationType} on the specified {@code element}, following
923+
* <em>get semantics</em>.
924+
* @param element the annotated element
925+
* @param annotationType the annotation type to find
926+
* @param annotationName the fully qualified class name of the annotation
927+
* type to find (as an alternative to {@code annotationType})
928+
* @param containerType the type of the container that holds repeatable
929+
* annotations, or {@code null} if the annotation is not repeatable
930+
* @param processor the processor to delegate to
931+
* @return the result of the processor, potentially {@code null}
932+
* @since 4.3
933+
*/
934+
private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
935+
String annotationName, Class<? extends Annotation> containerType, Processor<T> processor) {
837936

838937
try {
839-
return searchWithGetSemantics(
840-
element, annotationType, annotationName, processor, new HashSet<AnnotatedElement>(), 0);
938+
return searchWithGetSemantics(element, annotationType, annotationName, containerType, processor,
939+
new HashSet<AnnotatedElement>(), 0);
841940
}
842941
catch (Throwable ex) {
843942
AnnotationUtils.rethrowAnnotationConfigurationException(ex);
@@ -855,14 +954,16 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element,
855954
* @param annotationType the annotation type to find
856955
* @param annotationName the fully qualified class name of the annotation
857956
* type to find (as an alternative to {@code annotationType})
957+
* @param containerType the type of the container that holds repeatable
958+
* annotations, or {@code null} if the annotation is not repeatable
858959
* @param processor the processor to delegate to
859960
* @param visited the set of annotated elements that have already been visited
860961
* @param metaDepth the meta-depth of the annotation
861962
* @return the result of the processor, potentially {@code null}
862963
*/
863-
private static <T> T searchWithGetSemantics(AnnotatedElement element,
864-
Class<? extends Annotation> annotationType, String annotationName,
865-
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
964+
private static <T> T searchWithGetSemantics(AnnotatedElement element, Class<? extends Annotation> annotationType,
965+
String annotationName, Class<? extends Annotation> containerType, Processor<T> processor,
966+
Set<AnnotatedElement> visited, int metaDepth) {
866967

867968
Assert.notNull(element, "AnnotatedElement must not be null");
868969

@@ -871,12 +972,12 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element,
871972
// Start searching within locally declared annotations
872973
List<Annotation> declaredAnnotations = Arrays.asList(element.getDeclaredAnnotations());
873974
T result = searchWithGetSemanticsInAnnotations(element, declaredAnnotations,
874-
annotationType, annotationName, processor, visited, metaDepth);
975+
annotationType, annotationName, containerType, processor, visited, metaDepth);
875976
if (result != null) {
876977
return result;
877978
}
878979

879-
if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
980+
if (element instanceof Class) { // otherwise getAnnotations doesn't return anything new
880981
List<Annotation> inheritedAnnotations = new ArrayList<Annotation>();
881982
for (Annotation annotation : element.getAnnotations()) {
882983
if (!declaredAnnotations.contains(annotation)) {
@@ -886,7 +987,7 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element,
886987

887988
// Continue searching within inherited annotations
888989
result = searchWithGetSemanticsInAnnotations(element, inheritedAnnotations,
889-
annotationType, annotationName, processor, visited, metaDepth);
990+
annotationType, annotationName, containerType, processor, visited, metaDepth);
890991
if (result != null) {
891992
return result;
892993
}
@@ -915,6 +1016,8 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element,
9151016
* @param annotationType the annotation type to find
9161017
* @param annotationName the fully qualified class name of the annotation
9171018
* type to find (as an alternative to {@code annotationType})
1019+
* @param containerType the type of the container that holds repeatable
1020+
* annotations, or {@code null} if the annotation is not repeatable
9181021
* @param processor the processor to delegate to
9191022
* @param visited the set of annotated elements that have already been visited
9201023
* @param metaDepth the meta-depth of the annotation
@@ -923,21 +1026,39 @@ private static <T> T searchWithGetSemantics(AnnotatedElement element,
9231026
*/
9241027
private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annotatedElement,
9251028
List<Annotation> annotations, Class<? extends Annotation> annotationType, String annotationName,
926-
Processor<T> processor, Set<AnnotatedElement> visited, int metaDepth) {
1029+
Class<? extends Annotation> containerType, Processor<T> processor, Set<AnnotatedElement> visited,
1030+
int metaDepth) {
9271031

9281032
// Search in annotations
9291033
for (Annotation annotation : annotations) {
930-
// Note: we only check for (metaDepth > 0) due to the nuances of getMetaAnnotationTypes().
931-
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation) &&
932-
((annotation.annotationType() == annotationType
933-
|| annotation.annotationType().getName().equals(annotationName)) || metaDepth > 0)) {
934-
T result = processor.process(annotatedElement, annotation, metaDepth);
935-
if (result != null) {
936-
if (processor.aggregates() && metaDepth == 0) {
937-
processor.getAggregatedResults().add(result);
1034+
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
1035+
1036+
// TODO Check non-repeatable annotations first, once we have sorted out
1037+
// the metaDepth nuances of getMetaAnnotationTypes().
1038+
1039+
// Repeatable annotations in container?
1040+
if (annotation.annotationType() == containerType) {
1041+
for (Annotation contained : getRawAnnotationsFromContainer(annotatedElement, annotation)) {
1042+
T result = processor.process(annotatedElement, contained, metaDepth);
1043+
if (result != null) {
1044+
// No need to post-process since repeatable annotations within a
1045+
// container cannot be composed annotations.
1046+
processor.getAggregatedResults().add(result);
1047+
}
9381048
}
939-
else {
940-
return result;
1049+
}
1050+
else if ((annotation.annotationType() == annotationType
1051+
|| annotation.annotationType().getName().equals(annotationName)) || metaDepth > 0) {
1052+
1053+
// Note: we only check for (metaDepth > 0) due to the nuances of getMetaAnnotationTypes().
1054+
T result = processor.process(annotatedElement, annotation, metaDepth);
1055+
if (result != null) {
1056+
if (processor.aggregates() && metaDepth == 0) {
1057+
processor.getAggregatedResults().add(result);
1058+
}
1059+
else {
1060+
return result;
1061+
}
9411062
}
9421063
}
9431064
}
@@ -947,7 +1068,7 @@ private static <T> T searchWithGetSemanticsInAnnotations(AnnotatedElement annota
9471068
for (Annotation annotation : annotations) {
9481069
if (!AnnotationUtils.isInJavaLangAnnotationPackage(annotation)) {
9491070
T result = searchWithGetSemantics(annotation.annotationType(), annotationType,
950-
annotationName, processor, visited, metaDepth + 1);
1071+
annotationName, containerType, processor, visited, metaDepth + 1);
9511072
if (result != null) {
9521073
processor.postProcess(annotatedElement, annotation, result);
9531074
if (processor.aggregates() && metaDepth == 0) {

spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
* @since 4.0.3
5858
* @see AnnotationUtilsTests
5959
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
60+
* @see ComposedRepeatableAnnotationsTests
6061
*/
6162
public class AnnotatedElementUtilsTests {
6263

0 commit comments

Comments
 (0)