Resolve @Repeatable container in AnnotationUtils
This commit introduces support for automatically resolving a container annotation configured via @Repeatable in AnnotationUtils' getRepeatableAnnotations() and getDeclaredRepeatableAnnotations() methods. Issue: SPR-13068
This commit is contained in:
parent
80622803b2
commit
a0040245ca
|
|
@ -143,7 +143,7 @@ public class AnnotatedElementUtils {
|
|||
final Set<String> types = new LinkedHashSet<String>();
|
||||
|
||||
try {
|
||||
Annotation annotation = getAnnotation(element, annotationName);
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(element, annotationName);
|
||||
if (annotation != null) {
|
||||
searchWithGetSemantics(annotation.annotationType(), annotationName, new SimpleAnnotationProcessor<Object>() {
|
||||
|
||||
|
|
@ -834,19 +834,6 @@ public class AnnotatedElementUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 4.2
|
||||
*/
|
||||
private static Annotation getAnnotation(AnnotatedElement element, String annotationName) {
|
||||
for (Annotation annotation : element.getAnnotations()) {
|
||||
if (annotation.annotationType().getName().equals(annotationName)) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface that is used to process annotations during a search.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -250,10 +250,12 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #getRepeatableAnnotations}.
|
||||
* Delegates to {@link #getRepeatableAnnotations(AnnotatedElement, Class, Class)}.
|
||||
* @since 4.0
|
||||
* @deprecated As of Spring Framework 4.2, use {@link #getRepeatableAnnotations}
|
||||
* or {@link #getDeclaredRepeatableAnnotations} instead.
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @deprecated As of Spring Framework 4.2, use {@code getRepeatableAnnotations()}
|
||||
* or {@code getDeclaredRepeatableAnnotations()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <A extends Annotation> Set<A> getRepeatableAnnotation(Method method,
|
||||
|
|
@ -262,10 +264,12 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delegates to {@link #getRepeatableAnnotations}.
|
||||
* Delegates to {@link #getRepeatableAnnotations(AnnotatedElement, Class, Class)}.
|
||||
* @since 4.0
|
||||
* @deprecated As of Spring Framework 4.2, use {@link #getRepeatableAnnotations}
|
||||
* or {@link #getDeclaredRepeatableAnnotations} instead.
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @deprecated As of Spring Framework 4.2, use {@code getRepeatableAnnotations()}
|
||||
* or {@code getDeclaredRepeatableAnnotations()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static <A extends Annotation> Set<A> getRepeatableAnnotation(AnnotatedElement annotatedElement,
|
||||
|
|
@ -276,8 +280,39 @@ public abstract class AnnotationUtils {
|
|||
/**
|
||||
* Get the <em>repeatable</em> {@linkplain Annotation annotations} of
|
||||
* {@code annotationType} from the supplied {@link AnnotatedElement}, where
|
||||
* such annotations are either <em>present</em> or <em>meta-present</em>
|
||||
* on the element.
|
||||
* such annotations are either <em>present</em>, <em>indirectly present</em>,
|
||||
* or <em>meta-present</em> on the element.
|
||||
* <p>This method mimics the functionality of Java 8's
|
||||
* {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)}
|
||||
* with support for automatic detection of a <em>container annotation</em>
|
||||
* declared via @{@link java.lang.annotation.Repeatable} (when running on
|
||||
* Java 8 or higher) and with additional support for meta-annotations.
|
||||
* <p>Handles both single annotations and annotations nested within a
|
||||
* <em>container annotation</em>.
|
||||
* <p>Correctly handles <em>bridge methods</em> generated by the
|
||||
* compiler if the supplied element is a {@link Method}.
|
||||
* <p>Meta-annotations will be searched if the annotation is not
|
||||
* <em>present</em> on the supplied element.
|
||||
* @param annotatedElement the element to look for annotations on; never {@code null}
|
||||
* @param annotationType the annotation type to look for; never {@code null}
|
||||
* @return the annotations found or an empty set; never {@code null}
|
||||
* @since 4.2
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
|
||||
* @see java.lang.annotation.Repeatable
|
||||
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> getRepeatableAnnotations(AnnotatedElement annotatedElement,
|
||||
Class<A> annotationType) {
|
||||
return getRepeatableAnnotations(annotatedElement, annotationType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <em>repeatable</em> {@linkplain Annotation annotations} of
|
||||
* {@code annotationType} from the supplied {@link AnnotatedElement}, where
|
||||
* such annotations are either <em>present</em>, <em>indirectly present</em>,
|
||||
* or <em>meta-present</em> on the element.
|
||||
* <p>This method mimics the functionality of Java 8's
|
||||
* {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)}
|
||||
* with additional support for meta-annotations.
|
||||
|
|
@ -291,8 +326,12 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the annotation type to look for; never {@code null}
|
||||
* @param containerAnnotationType the type of the container that holds
|
||||
* the annotations; may be {@code null} if a container is not supported
|
||||
* or if it should be looked up via @{@link java.lang.annotation.Repeatable}
|
||||
* when running on Java 8 or higher
|
||||
* @return the annotations found or an empty set; never {@code null}
|
||||
* @since 4.2
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
|
||||
* @see java.lang.annotation.Repeatable
|
||||
|
|
@ -316,6 +355,38 @@ public abstract class AnnotationUtils {
|
|||
return getRepeatableAnnotations(annotatedElement, annotationType, containerAnnotationType, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the declared <em>repeatable</em> {@linkplain Annotation annotations}
|
||||
* of {@code annotationType} from the supplied {@link AnnotatedElement},
|
||||
* where such annotations are either <em>directly present</em>,
|
||||
* <em>indirectly present</em>, or <em>meta-present</em> on the element.
|
||||
* <p>This method mimics the functionality of Java 8's
|
||||
* {@link java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType(Class)}
|
||||
* with support for automatic detection of a <em>container annotation</em>
|
||||
* declared via @{@link java.lang.annotation.Repeatable} (when running on
|
||||
* Java 8 or higher) and with additional support for meta-annotations.
|
||||
* <p>Handles both single annotations and annotations nested within a
|
||||
* <em>container annotation</em>.
|
||||
* <p>Correctly handles <em>bridge methods</em> generated by the
|
||||
* compiler if the supplied element is a {@link Method}.
|
||||
* <p>Meta-annotations will be searched if the annotation is not
|
||||
* <em>present</em> on the supplied element.
|
||||
* @param annotatedElement the element to look for annotations on; never {@code null}
|
||||
* @param annotationType the annotation type to look for; never {@code null}
|
||||
* @return the annotations found or an empty set; never {@code null}
|
||||
* @since 4.2
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
|
||||
* @see java.lang.annotation.Repeatable
|
||||
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
|
||||
*/
|
||||
public static <A extends Annotation> Set<A> getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement,
|
||||
Class<A> annotationType) {
|
||||
return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the declared <em>repeatable</em> {@linkplain Annotation annotations}
|
||||
* of {@code annotationType} from the supplied {@link AnnotatedElement},
|
||||
|
|
@ -334,9 +405,13 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the annotation type to look for; never {@code null}
|
||||
* @param containerAnnotationType the type of the container that holds
|
||||
* the annotations; may be {@code null} if a container is not supported
|
||||
* or if it should be looked up via @{@link java.lang.annotation.Repeatable}
|
||||
* when running on Java 8 or higher
|
||||
* @return the annotations found or an empty set; never {@code null}
|
||||
* @since 4.2
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
|
||||
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
|
||||
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
|
||||
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
|
||||
* @see java.lang.annotation.Repeatable
|
||||
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
|
||||
|
|
@ -358,6 +433,8 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the annotation type to look for; never {@code null}
|
||||
* @param containerAnnotationType the type of the container that holds
|
||||
* the annotations; may be {@code null} if a container is not supported
|
||||
* or if it should be looked up via @{@link java.lang.annotation.Repeatable}
|
||||
* when running on Java 8 or higher
|
||||
* @param declaredMode {@code true} if only declared annotations (i.e.,
|
||||
* directly or indirectly present) should be considered.
|
||||
* @return the annotations found or an empty set; never {@code null}
|
||||
|
|
@ -1489,6 +1566,24 @@ public abstract class AnnotationUtils {
|
|||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the annotation with the supplied {@code annotationName} on the
|
||||
* supplied {@code element}.
|
||||
* @param element the element to search on
|
||||
* @param annotationName the fully qualified class name of the annotation
|
||||
* type to find; never {@code null} or empty
|
||||
* @return the annotation if found; {@code null} otherwise
|
||||
* @since 4.2
|
||||
*/
|
||||
static Annotation getAnnotation(AnnotatedElement element, String annotationName) {
|
||||
for (Annotation annotation : element.getAnnotations()) {
|
||||
if (annotation.annotationType().getName().equals(annotationName)) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@code method} is an annotation attribute method.
|
||||
* @param method the method to check
|
||||
|
|
@ -1673,6 +1768,9 @@ public abstract class AnnotationUtils {
|
|||
|
||||
private static class AnnotationCollector<A extends Annotation> {
|
||||
|
||||
private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
|
||||
|
||||
|
||||
private final Class<A> annotationType;
|
||||
|
||||
private final Class<? extends Annotation> containerAnnotationType;
|
||||
|
|
@ -1686,10 +1784,26 @@ public abstract class AnnotationUtils {
|
|||
|
||||
AnnotationCollector(Class<A> annotationType, Class<? extends Annotation> containerAnnotationType, boolean declaredMode) {
|
||||
this.annotationType = annotationType;
|
||||
this.containerAnnotationType = containerAnnotationType;
|
||||
this.containerAnnotationType = (containerAnnotationType != null ? containerAnnotationType
|
||||
: resolveContainerAnnotationType(annotationType));
|
||||
this.declaredMode = declaredMode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
|
||||
try {
|
||||
Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
|
||||
if (repeatable != null) {
|
||||
Object value = AnnotationUtils.getValue(repeatable);
|
||||
return (Class<? extends Annotation>) value;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
handleIntrospectionFailure(annotationType, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<A> getResult(AnnotatedElement element) {
|
||||
process(element);
|
||||
return Collections.unmodifiableSet(this.result);
|
||||
|
|
@ -1722,12 +1836,8 @@ public abstract class AnnotationUtils {
|
|||
@SuppressWarnings("unchecked")
|
||||
private List<A> getValue(AnnotatedElement element, Annotation annotation) {
|
||||
try {
|
||||
Method method = annotation.annotationType().getDeclaredMethod("value");
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
A[] annotations = (A[]) method.invoke(annotation);
|
||||
|
||||
List<A> synthesizedAnnotations = new ArrayList<A>();
|
||||
for (A anno : annotations) {
|
||||
for (A anno : (A[]) AnnotationUtils.getValue(annotation)) {
|
||||
synthesizedAnnotations.add(synthesizeAnnotation(anno, element));
|
||||
}
|
||||
return synthesizedAnnotations;
|
||||
|
|
|
|||
|
|
@ -520,7 +520,11 @@ public class AnnotationUtilsTests {
|
|||
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception {
|
||||
final List<String> expectedLocations = Arrays.asList("A", "B");
|
||||
|
||||
Set<ContextConfig> annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, Hierarchy.class);
|
||||
Set<ContextConfig> annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, null);
|
||||
assertNotNull(annotations);
|
||||
assertEquals("size if container type is omitted: ", 0, annotations.size());
|
||||
|
||||
annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, Hierarchy.class);
|
||||
assertNotNull(annotations);
|
||||
|
||||
List<String> locations = annotations.stream().map(ContextConfig::locations).collect(toList());
|
||||
|
|
@ -546,6 +550,12 @@ public class AnnotationUtilsTests {
|
|||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getRepeatableAnnotations(MyRepeatableClass.class, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -565,6 +575,12 @@ public class AnnotationUtilsTests {
|
|||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getRepeatableAnnotations(clazz, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -584,6 +600,12 @@ public class AnnotationUtilsTests {
|
|||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getRepeatableAnnotations(clazz, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -603,6 +625,12 @@ public class AnnotationUtilsTests {
|
|||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getRepeatableAnnotations(clazz, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -621,6 +649,12 @@ public class AnnotationUtilsTests {
|
|||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getDeclaredRepeatableAnnotations(MyRepeatableClass.class, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
values = set.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(expectedValuesSpring));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -636,6 +670,11 @@ public class AnnotationUtilsTests {
|
|||
Set<MyRepeatable> set = getDeclaredRepeatableAnnotations(clazz, MyRepeatable.class, MyRepeatableContainer.class);
|
||||
assertNotNull(set);
|
||||
assertThat(set.size(), is(0));
|
||||
|
||||
// When container type is omitted and therefore inferred from @Repeatable
|
||||
set = getDeclaredRepeatableAnnotations(clazz, MyRepeatable.class);
|
||||
assertNotNull(set);
|
||||
assertThat(set.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue