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:
Sam Brannen 2015-06-20 17:52:57 +02:00
parent 80622803b2
commit a0040245ca
3 changed files with 165 additions and 29 deletions

View File

@ -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.
*

View File

@ -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;

View File

@ -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