Support searches for merged repeatable annotations with "get" semantics

This commit picks up where 2535469099 left off with added support for
"get" search semantics for merged repeatable annotations.

Specifically, this commit introduces a new
getMergedRepeatableAnnotations() method in AnnotatedElementUtils.

Issue: SPR-13973
This commit is contained in:
Sam Brannen
2016-03-24 19:23:32 +01:00
parent 8d0083ca96
commit 46e0484bf8
4 changed files with 325 additions and 46 deletions

View File

@@ -57,6 +57,7 @@ import static org.springframework.core.annotation.AnnotationUtilsTests.*;
* @since 4.0.3
* @see AnnotationUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
* @see ComposedRepeatableAnnotationsTests
*/
public class AnnotatedElementUtilsTests {

View File

@@ -37,15 +37,17 @@ import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
/**
* Unit tests that verify support for finding all composed, repeatable
* Unit tests that verify support for getting and finding all composed, repeatable
* annotations on a single annotated element.
*
* <p>See <a href="https://jira.spring.io/browse/SPR-13973">SPR-13973</a>.
*
* @author Sam Brannen
* @since 4.3
* @see AnnotatedElementUtils
* @see AnnotatedElementUtils#getMergedRepeatableAnnotations
* @see AnnotatedElementUtils#findMergedRepeatableAnnotations
* @see AnnotatedElementUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
*/
public class ComposedRepeatableAnnotationsTests {
@@ -53,43 +55,92 @@ public class ComposedRepeatableAnnotationsTests {
public final ExpectedException exception = ExpectedException.none();
@Test
public void getNonRepeatableAnnotation() {
expectNonRepeatableAnnotation();
getMergedRepeatableAnnotations(getClass(), NonRepeatable.class);
}
@Test
public void getInvalidRepeatableAnnotationContainerMissingValueAttribute() {
expectContainerMissingValueAttribute();
getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class);
}
@Test
public void getInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() {
expectContainerWithNonArrayValueAttribute();
getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class);
}
@Test
public void getInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() {
expectContainerWithArrayValueAttributeButWrongComponentType();
getMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class,
ContainerWithArrayValueAttributeButWrongComponentType.class);
}
@Test
public void getRepeatableAnnotationsOnClass() {
assertGetRepeatableAnnotations(RepeatableClass.class);
}
@Test
public void getRepeatableAnnotationsOnSuperclass() {
assertGetRepeatableAnnotations(SubRepeatableClass.class);
}
@Test
public void getComposedRepeatableAnnotationsOnClass() {
assertGetRepeatableAnnotations(ComposedRepeatableClass.class);
}
@Test
public void getComposedRepeatableAnnotationsMixedWithContainerOnClass() {
assertGetRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class);
}
@Test
public void getComposedContainerForRepeatableAnnotationsOnClass() {
assertGetRepeatableAnnotations(ComposedContainerClass.class);
}
@Test
public void getNoninheritedComposedRepeatableAnnotationsOnClass() {
Class<?> element = NoninheritedRepeatableClass.class;
Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class);
assertNoninheritedRepeatableAnnotations(annotations);
}
@Test
public void getNoninheritedComposedRepeatableAnnotationsOnSuperclass() {
Class<?> element = SubNoninheritedRepeatableClass.class;
Set<Noninherited> annotations = getMergedRepeatableAnnotations(element, Noninherited.class);
assertNotNull(annotations);
assertEquals(0, annotations.size());
}
@Test
public void findNonRepeatableAnnotation() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("annotationType must be a repeatable annotation"));
exception.expectMessage(containsString("failed to resolve container type for"));
exception.expectMessage(containsString(NonRepeatable.class.getName()));
expectNonRepeatableAnnotation();
findMergedRepeatableAnnotations(getClass(), NonRepeatable.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerMissingValueAttribute() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Invalid declaration of container type"));
exception.expectMessage(containsString(ContainerMissingValueAttribute.class.getName()));
exception.expectMessage(containsString("for repeatable annotation"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
exception.expectCause(isA(NoSuchMethodException.class));
expectContainerMissingValueAttribute();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Container type"));
exception.expectMessage(containsString(ContainerWithNonArrayValueAttribute.class.getName()));
exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
expectContainerWithNonArrayValueAttribute();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Container type"));
exception.expectMessage(containsString(ContainerWithArrayValueAttributeButWrongComponentType.class.getName()));
exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
expectContainerWithArrayValueAttributeButWrongComponentType();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class,
ContainerWithArrayValueAttributeButWrongComponentType.class);
}
@@ -114,11 +165,70 @@ public class ComposedRepeatableAnnotationsTests {
assertFindRepeatableAnnotations(ComposedRepeatableMixedWithContainerClass.class);
}
@Test
public void findNoninheritedComposedRepeatableAnnotationsOnClass() {
Class<?> element = NoninheritedRepeatableClass.class;
Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class);
assertNoninheritedRepeatableAnnotations(annotations);
}
@Test
public void findNoninheritedComposedRepeatableAnnotationsOnSuperclass() {
Class<?> element = SubNoninheritedRepeatableClass.class;
Set<Noninherited> annotations = findMergedRepeatableAnnotations(element, Noninherited.class);
assertNoninheritedRepeatableAnnotations(annotations);
}
@Test
public void findComposedContainerForRepeatableAnnotationsOnClass() {
assertFindRepeatableAnnotations(ComposedContainerClass.class);
}
private void expectNonRepeatableAnnotation() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("annotationType must be a repeatable annotation"));
exception.expectMessage(containsString("failed to resolve container type for"));
exception.expectMessage(containsString(NonRepeatable.class.getName()));
}
private void expectContainerMissingValueAttribute() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Invalid declaration of container type"));
exception.expectMessage(containsString(ContainerMissingValueAttribute.class.getName()));
exception.expectMessage(containsString("for repeatable annotation"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
exception.expectCause(isA(NoSuchMethodException.class));
}
private void expectContainerWithNonArrayValueAttribute() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Container type"));
exception.expectMessage(containsString(ContainerWithNonArrayValueAttribute.class.getName()));
exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
}
private void expectContainerWithArrayValueAttributeButWrongComponentType() {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Container type"));
exception.expectMessage(containsString(ContainerWithArrayValueAttributeButWrongComponentType.class.getName()));
exception.expectMessage(containsString("must declare a 'value' attribute for an array of type"));
exception.expectMessage(containsString(InvalidRepeatable.class.getName()));
}
private void assertGetRepeatableAnnotations(AnnotatedElement element) {
assertNotNull(element);
Set<PeteRepeat> peteRepeats = getMergedRepeatableAnnotations(element, PeteRepeat.class);
assertNotNull(peteRepeats);
assertEquals(3, peteRepeats.size());
Iterator<PeteRepeat> iterator = peteRepeats.iterator();
assertEquals("A", iterator.next().value());
assertEquals("B", iterator.next().value());
assertEquals("C", iterator.next().value());
}
private void assertFindRepeatableAnnotations(AnnotatedElement element) {
assertNotNull(element);
@@ -132,6 +242,16 @@ public class ComposedRepeatableAnnotationsTests {
assertEquals("C", iterator.next().value());
}
private void assertNoninheritedRepeatableAnnotations(Set<Noninherited> annotations) {
assertNotNull(annotations);
assertEquals(3, annotations.size());
Iterator<Noninherited> iterator = annotations.iterator();
assertEquals("A", iterator.next().value());
assertEquals("B", iterator.next().value());
assertEquals("C", iterator.next().value());
}
// -------------------------------------------------------------------------
@@ -227,4 +347,40 @@ public class ComposedRepeatableAnnotationsTests {
static class ComposedContainerClass {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Noninheriteds {
Noninherited[] value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Noninheriteds.class)
@interface Noninherited {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
}
@Noninherited(name = "shadowed")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface ComposedNoninherited {
@AliasFor(annotation = Noninherited.class)
String name() default "";
}
@ComposedNoninherited(name = "C")
@Noninheriteds({ @Noninherited(value = "A"), @Noninherited(name = "B") })
static class NoninheritedRepeatableClass {
}
static class SubNoninheritedRepeatableClass extends NoninheritedRepeatableClass {
}
}

View File

@@ -42,6 +42,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.*;
* @since 4.3
* @see AnnotatedElementUtils
* @see AnnotatedElementUtilsTests
* @see ComposedRepeatableAnnotationsTests
*/
public class MultipleComposedAnnotationsOnSingleAnnotatedElementTests {