From a0040245ca3bf968aec55db00f401e10b71a875f Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 20 Jun 2015 17:52:57 +0200 Subject: [PATCH] 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 --- .../annotation/AnnotatedElementUtils.java | 15 +- .../core/annotation/AnnotationUtils.java | 138 ++++++++++++++++-- .../core/annotation/AnnotationUtilsTests.java | 41 +++++- 3 files changed, 165 insertions(+), 29 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 427489364f1..e4acbbcdf96 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -143,7 +143,7 @@ public class AnnotatedElementUtils { final Set types = new LinkedHashSet(); try { - Annotation annotation = getAnnotation(element, annotationName); + Annotation annotation = AnnotationUtils.getAnnotation(element, annotationName); if (annotation != null) { searchWithGetSemantics(annotation.annotationType(), annotationName, new SimpleAnnotationProcessor() { @@ -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. * diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index 19ca446dfdb..e4469e5c0d8 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -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 Set 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 Set getRepeatableAnnotation(AnnotatedElement annotatedElement, @@ -276,8 +280,39 @@ public abstract class AnnotationUtils { /** * Get the repeatable {@linkplain Annotation annotations} of * {@code annotationType} from the supplied {@link AnnotatedElement}, where - * such annotations are either present or meta-present - * on the element. + * such annotations are either present, indirectly present, + * or meta-present on the element. + *

This method mimics the functionality of Java 8's + * {@link java.lang.reflect.AnnotatedElement#getAnnotationsByType(Class)} + * with support for automatic detection of a container annotation + * declared via @{@link java.lang.annotation.Repeatable} (when running on + * Java 8 or higher) and with additional support for meta-annotations. + *

Handles both single annotations and annotations nested within a + * container annotation. + *

Correctly handles bridge methods generated by the + * compiler if the supplied element is a {@link Method}. + *

Meta-annotations will be searched if the annotation is not + * present 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 Set getRepeatableAnnotations(AnnotatedElement annotatedElement, + Class annotationType) { + return getRepeatableAnnotations(annotatedElement, annotationType, null); + } + + /** + * Get the repeatable {@linkplain Annotation annotations} of + * {@code annotationType} from the supplied {@link AnnotatedElement}, where + * such annotations are either present, indirectly present, + * or meta-present on the element. *

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 repeatable {@linkplain Annotation annotations} + * of {@code annotationType} from the supplied {@link AnnotatedElement}, + * where such annotations are either directly present, + * indirectly present, or meta-present on the element. + *

This method mimics the functionality of Java 8's + * {@link java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType(Class)} + * with support for automatic detection of a container annotation + * declared via @{@link java.lang.annotation.Repeatable} (when running on + * Java 8 or higher) and with additional support for meta-annotations. + *

Handles both single annotations and annotations nested within a + * container annotation. + *

Correctly handles bridge methods generated by the + * compiler if the supplied element is a {@link Method}. + *

Meta-annotations will be searched if the annotation is not + * present 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 Set getDeclaredRepeatableAnnotations(AnnotatedElement annotatedElement, + Class annotationType) { + return getDeclaredRepeatableAnnotations(annotatedElement, annotationType, null); + } + /** * Get the declared repeatable {@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 { + private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable"; + + private final Class annotationType; private final Class containerAnnotationType; @@ -1686,10 +1784,26 @@ public abstract class AnnotationUtils { AnnotationCollector(Class annotationType, Class containerAnnotationType, boolean declaredMode) { this.annotationType = annotationType; - this.containerAnnotationType = containerAnnotationType; + this.containerAnnotationType = (containerAnnotationType != null ? containerAnnotationType + : resolveContainerAnnotationType(annotationType)); this.declaredMode = declaredMode; } + @SuppressWarnings("unchecked") + static Class resolveContainerAnnotationType(Class annotationType) { + try { + Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME); + if (repeatable != null) { + Object value = AnnotationUtils.getValue(repeatable); + return (Class) value; + } + } + catch (Exception e) { + handleIntrospectionFailure(annotationType, e); + } + return null; + } + Set getResult(AnnotatedElement element) { process(element); return Collections.unmodifiableSet(this.result); @@ -1722,12 +1836,8 @@ public abstract class AnnotationUtils { @SuppressWarnings("unchecked") private List getValue(AnnotatedElement element, Annotation annotation) { try { - Method method = annotation.annotationType().getDeclaredMethod("value"); - ReflectionUtils.makeAccessible(method); - A[] annotations = (A[]) method.invoke(annotation); - List synthesizedAnnotations = new ArrayList(); - for (A anno : annotations) { + for (A anno : (A[]) AnnotationUtils.getValue(annotation)) { synthesizedAnnotations.add(synthesizeAnnotation(anno, element)); } return synthesizedAnnotations; diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java index 8d22bc92ce8..4c363e71596 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java @@ -520,7 +520,11 @@ public class AnnotationUtilsTests { public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception { final List expectedLocations = Arrays.asList("A", "B"); - Set annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, Hierarchy.class); + Set 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 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 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