Skip to content

Commit 3b145a5

Browse files
philwebbjhoeller
authored andcommitted
Add MergedAnnotation.getTypeHierarchy method
Add a `getTypeHierarchy()` method to `MergedAnnotation` that can be used to return the full type hierarchy information. This method is specifically designed to be used in combination with `MergedAnnotationPredicates.unique`. This update also allows us to delete the `parentAndType` method from `AnnotatedElementUtils`. Closes gh-22908
1 parent f86affe commit 3b145a5

File tree

8 files changed

+65
-8
lines changed

8 files changed

+65
-8
lines changed

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ public static MultiValueMap<String, Object> getAllAnnotationAttributes(Annotated
502502

503503
Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap);
504504
return getAnnotations(element).stream(annotationName)
505-
.filter(MergedAnnotationPredicates.unique(AnnotatedElementUtils::parentAndType))
505+
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getTypeHierarchy))
506506
.map(MergedAnnotation::withNonMergedAttributes)
507507
.collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, adaptations));
508508
}
@@ -775,13 +775,6 @@ private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement elem
775775
repeatableContainers, AnnotationFilter.PLAIN);
776776
}
777777

778-
private static Object parentAndType(MergedAnnotation<Annotation> annotation) {
779-
if (annotation.getParent() == null) {
780-
return annotation.getType().getName();
781-
}
782-
return annotation.getParent().getType().getName() + ":" + annotation.getParent().getType().getName();
783-
}
784-
785778
@Nullable
786779
private static MultiValueMap<String, Object> nullIfEmpty(MultiValueMap<String, Object> map) {
787780
return (map.isEmpty() ? null : map);

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ final class AnnotationTypeMapping {
5757

5858
private final Class<? extends Annotation> annotationType;
5959

60+
private final List<Class<? extends Annotation>> annotationTypeHierarchy;
61+
6062
@Nullable
6163
private final Annotation annotation;
6264

@@ -84,6 +86,9 @@ final class AnnotationTypeMapping {
8486
this.root = (parent != null ? parent.getRoot() : this);
8587
this.depth = (parent == null ? 0 : parent.getDepth() + 1);
8688
this.annotationType = annotationType;
89+
this.annotationTypeHierarchy = merge(
90+
parent != null ? parent.getAnnotationTypeHierarchy() : null,
91+
annotationType);
8792
this.annotation = annotation;
8893
this.attributes = AttributeMethods.forAnnotationType(annotationType);
8994
this.mirrorSets = new MirrorSets();
@@ -98,6 +103,16 @@ final class AnnotationTypeMapping {
98103
}
99104

100105

106+
private static <T> List<T> merge(@Nullable List<T> existing, T element) {
107+
if (existing == null) {
108+
return Collections.singletonList(element);
109+
}
110+
List<T> merged = new ArrayList<>(existing.size() + 1);
111+
merged.addAll(existing);
112+
merged.add(element);
113+
return Collections.unmodifiableList(merged);
114+
}
115+
101116
private Map<Method, List<Method>> resolveAliasedForTargets() {
102117
Map<Method, List<Method>> aliasedBy = new HashMap<>();
103118
for (int i = 0; i < this.attributes.size(); i++) {
@@ -372,6 +387,10 @@ Class<? extends Annotation> getAnnotationType() {
372387
return this.annotationType;
373388
}
374389

390+
List<Class<? extends Annotation>> getAnnotationTypeHierarchy() {
391+
return this.annotationTypeHierarchy;
392+
}
393+
375394
/**
376395
* Get the source annotation for this mapping. This will be the
377396
* meta-annotation, or {@code null} if this is the root mapping.

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.AnnotatedElement;
2222
import java.lang.reflect.Proxy;
2323
import java.util.EnumSet;
24+
import java.util.List;
2425
import java.util.Map;
2526
import java.util.NoSuchElementException;
2627
import java.util.Optional;
@@ -72,6 +73,15 @@ public interface MergedAnnotation<A extends Annotation> {
7273
*/
7374
Class<A> getType();
7475

76+
/**
77+
* Return a complete type hierarchy from this annotation to the
78+
* {@link #getRoot() root}. Provides a useful way to uniquely identify a
79+
* merged annotation instance.
80+
* @return the type heirarchy for the annotation
81+
* @see MergedAnnotationPredicates#unique(Function)
82+
*/
83+
List<Class<? extends Annotation>> getTypeHierarchy();
84+
7585
/**
7686
* Determine if the annotation is present on the source. Considers
7787
* {@linkplain #isDirectlyPresent() directly present} and

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.util.Collections;
21+
import java.util.List;
2122
import java.util.Map;
2223
import java.util.NoSuchElementException;
2324
import java.util.Optional;
@@ -49,6 +50,11 @@ public Class<A> getType() {
4950
throw new NoSuchElementException("Unable to get type for missing annotation");
5051
}
5152

53+
@Override
54+
public List<Class<? extends Annotation>> getTypeHierarchy() {
55+
return Collections.emptyList();
56+
}
57+
5258
@Override
5359
public boolean isPresent() {
5460
return false;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.Method;
2222
import java.util.Collections;
2323
import java.util.LinkedHashMap;
24+
import java.util.List;
2425
import java.util.Map;
2526
import java.util.NoSuchElementException;
2627
import java.util.Optional;
@@ -139,6 +140,11 @@ public Class<A> getType() {
139140
return (Class<A>) this.mapping.getAnnotationType();
140141
}
141142

143+
@Override
144+
public List<Class<? extends Annotation>> getTypeHierarchy() {
145+
return this.mapping.getAnnotationTypeHierarchy();
146+
}
147+
142148
@Override
143149
public boolean isPresent() {
144150
return true;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,15 @@ public void getAnnotationTypeReturnsAnnotationType() {
245245
assertThat(mappings.get(1).getAnnotationType()).isEqualTo(MappedTarget.class);
246246
}
247247

248+
@Test
249+
public void getAnnotationTypeHierarchyReturnsTypeHierarchy() {
250+
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
251+
ThreeDeepA.class);
252+
AnnotationTypeMapping mappingC = mappings.get(2);
253+
assertThat(mappingC.getAnnotationTypeHierarchy()).containsExactly(
254+
ThreeDeepA.class, ThreeDeepB.class, ThreeDeepC.class);
255+
}
256+
248257
@Test
249258
public void getAnnotationWhenRootReturnsNull() {
250259
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ public void getRootWhenDirect() {
170170
assertThat(annotation.getRoot()).isSameAs(annotation);
171171
}
172172

173+
@Test
174+
public void getTypeHierarchy() {
175+
MergedAnnotation<?> annotation = MergedAnnotations.from(
176+
ComposedTransactionalComponentClass.class).get(
177+
TransactionalComponent.class);
178+
assertThat(annotation.getTypeHierarchy()).containsExactly(
179+
ComposedTransactionalComponent.class, TransactionalComponent.class);
180+
}
181+
173182
@Test
174183
public void collectMultiValueMapFromNonAnnotatedClass() {
175184
MultiValueMap<String, Object> map = MergedAnnotations.from(

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ public void getTypeThrowsNoSuchElementException() {
4444
assertThatNoSuchElementException().isThrownBy(this.missing::getType);
4545
}
4646

47+
@Test
48+
public void getTypeHierarchyReturnsEmptyList() {
49+
assertThat(this.missing.getTypeHierarchy()).isEmpty();
50+
}
51+
4752
@Test
4853
public void isPresentReturnsFalse() {
4954
assertThat(this.missing.isPresent()).isFalse();

0 commit comments

Comments
 (0)