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
This commit is contained in:
parent
f86affe404
commit
3b145a5a73
|
@ -502,7 +502,7 @@ public abstract class AnnotatedElementUtils {
|
|||
|
||||
Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap);
|
||||
return getAnnotations(element).stream(annotationName)
|
||||
.filter(MergedAnnotationPredicates.unique(AnnotatedElementUtils::parentAndType))
|
||||
.filter(MergedAnnotationPredicates.unique(MergedAnnotation::getTypeHierarchy))
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
.collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, adaptations));
|
||||
}
|
||||
|
@ -775,13 +775,6 @@ public abstract class AnnotatedElementUtils {
|
|||
repeatableContainers, AnnotationFilter.PLAIN);
|
||||
}
|
||||
|
||||
private static Object parentAndType(MergedAnnotation<Annotation> annotation) {
|
||||
if (annotation.getParent() == null) {
|
||||
return annotation.getType().getName();
|
||||
}
|
||||
return annotation.getParent().getType().getName() + ":" + annotation.getParent().getType().getName();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static MultiValueMap<String, Object> nullIfEmpty(MultiValueMap<String, Object> map) {
|
||||
return (map.isEmpty() ? null : map);
|
||||
|
|
|
@ -57,6 +57,8 @@ final class AnnotationTypeMapping {
|
|||
|
||||
private final Class<? extends Annotation> annotationType;
|
||||
|
||||
private final List<Class<? extends Annotation>> annotationTypeHierarchy;
|
||||
|
||||
@Nullable
|
||||
private final Annotation annotation;
|
||||
|
||||
|
@ -84,6 +86,9 @@ final class AnnotationTypeMapping {
|
|||
this.root = (parent != null ? parent.getRoot() : this);
|
||||
this.depth = (parent == null ? 0 : parent.getDepth() + 1);
|
||||
this.annotationType = annotationType;
|
||||
this.annotationTypeHierarchy = merge(
|
||||
parent != null ? parent.getAnnotationTypeHierarchy() : null,
|
||||
annotationType);
|
||||
this.annotation = annotation;
|
||||
this.attributes = AttributeMethods.forAnnotationType(annotationType);
|
||||
this.mirrorSets = new MirrorSets();
|
||||
|
@ -98,6 +103,16 @@ final class AnnotationTypeMapping {
|
|||
}
|
||||
|
||||
|
||||
private static <T> List<T> merge(@Nullable List<T> existing, T element) {
|
||||
if (existing == null) {
|
||||
return Collections.singletonList(element);
|
||||
}
|
||||
List<T> merged = new ArrayList<>(existing.size() + 1);
|
||||
merged.addAll(existing);
|
||||
merged.add(element);
|
||||
return Collections.unmodifiableList(merged);
|
||||
}
|
||||
|
||||
private Map<Method, List<Method>> resolveAliasedForTargets() {
|
||||
Map<Method, List<Method>> aliasedBy = new HashMap<>();
|
||||
for (int i = 0; i < this.attributes.size(); i++) {
|
||||
|
@ -372,6 +387,10 @@ final class AnnotationTypeMapping {
|
|||
return this.annotationType;
|
||||
}
|
||||
|
||||
List<Class<? extends Annotation>> getAnnotationTypeHierarchy() {
|
||||
return this.annotationTypeHierarchy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source annotation for this mapping. This will be the
|
||||
* meta-annotation, or {@code null} if this is the root mapping.
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.lang.annotation.Inherited;
|
|||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
@ -72,6 +73,15 @@ public interface MergedAnnotation<A extends Annotation> {
|
|||
*/
|
||||
Class<A> getType();
|
||||
|
||||
/**
|
||||
* Return a complete type hierarchy from this annotation to the
|
||||
* {@link #getRoot() root}. Provides a useful way to uniquely identify a
|
||||
* merged annotation instance.
|
||||
* @return the type heirarchy for the annotation
|
||||
* @see MergedAnnotationPredicates#unique(Function)
|
||||
*/
|
||||
List<Class<? extends Annotation>> getTypeHierarchy();
|
||||
|
||||
/**
|
||||
* Determine if the annotation is present on the source. Considers
|
||||
* {@linkplain #isDirectlyPresent() directly present} and
|
||||
|
|
|
@ -18,6 +18,7 @@ package org.springframework.core.annotation;
|
|||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
@ -49,6 +50,11 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
|
|||
throw new NoSuchElementException("Unable to get type for missing annotation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Annotation>> getTypeHierarchy() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return false;
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.lang.reflect.Array;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
@ -139,6 +140,11 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
|||
return (Class<A>) this.mapping.getAnnotationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Class<? extends Annotation>> getTypeHierarchy() {
|
||||
return this.mapping.getAnnotationTypeHierarchy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresent() {
|
||||
return true;
|
||||
|
|
|
@ -245,6 +245,15 @@ public class AnnotationTypeMappingsTests {
|
|||
assertThat(mappings.get(1).getAnnotationType()).isEqualTo(MappedTarget.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationTypeHierarchyReturnsTypeHierarchy() {
|
||||
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
|
||||
ThreeDeepA.class);
|
||||
AnnotationTypeMapping mappingC = mappings.get(2);
|
||||
assertThat(mappingC.getAnnotationTypeHierarchy()).containsExactly(
|
||||
ThreeDeepA.class, ThreeDeepB.class, ThreeDeepC.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAnnotationWhenRootReturnsNull() {
|
||||
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(
|
||||
|
|
|
@ -170,6 +170,15 @@ public class MergedAnnotationsTests {
|
|||
assertThat(annotation.getRoot()).isSameAs(annotation);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTypeHierarchy() {
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(
|
||||
ComposedTransactionalComponentClass.class).get(
|
||||
TransactionalComponent.class);
|
||||
assertThat(annotation.getTypeHierarchy()).containsExactly(
|
||||
ComposedTransactionalComponent.class, TransactionalComponent.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void collectMultiValueMapFromNonAnnotatedClass() {
|
||||
MultiValueMap<String, Object> map = MergedAnnotations.from(
|
||||
|
|
|
@ -44,6 +44,11 @@ public class MissingMergedAnnotationTests {
|
|||
assertThatNoSuchElementException().isThrownBy(this.missing::getType);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTypeHierarchyReturnsEmptyList() {
|
||||
assertThat(this.missing.getTypeHierarchy()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isPresentReturnsFalse() {
|
||||
assertThat(this.missing.isPresent()).isFalse();
|
||||
|
|
Loading…
Reference in New Issue