/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Iterator;
import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.hamcrest.CoreMatchers.isA;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotatedElementUtils.*;
/**
* 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#getMergedRepeatableAnnotations
* @see AnnotatedElementUtils#findMergedRepeatableAnnotations
* @see AnnotatedElementUtilsTests
* @see MultipleComposedAnnotationsOnSingleAnnotatedElementTests
*/
public class ComposedRepeatableAnnotationsTests {
@Rule
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() {
expectNonRepeatableAnnotation();
findMergedRepeatableAnnotations(getClass(), NonRepeatable.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerMissingValueAttribute() {
expectContainerMissingValueAttribute();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerMissingValueAttribute.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerWithNonArrayValueAttribute() {
expectContainerWithNonArrayValueAttribute();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class, ContainerWithNonArrayValueAttribute.class);
}
@Test
public void findInvalidRepeatableAnnotationContainerWithArrayValueAttributeButWrongComponentType() {
expectContainerWithArrayValueAttributeButWrongComponentType();
findMergedRepeatableAnnotations(getClass(), InvalidRepeatable.class,
ContainerWithArrayValueAttributeButWrongComponentType.class);
}
@Test
public void findRepeatableAnnotationsOnClass() {
assertFindRepeatableAnnotations(RepeatableClass.class);
}
@Test
public void findRepeatableAnnotationsOnSuperclass() {
assertFindRepeatableAnnotations(SubRepeatableClass.class);
}
@Test
public void findComposedRepeatableAnnotationsOnClass() {
assertFindRepeatableAnnotations(ComposedRepeatableClass.class);
}
@Test
public void findComposedRepeatableAnnotationsMixedWithContainerOnClass() {
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("Annotation type 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);
Set<PeteRepeat> peteRepeats = findMergedRepeatableAnnotations(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 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());
}
// -------------------------------------------------------------------------
@Retention(RetentionPolicy.RUNTIME)
@interface NonRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerMissingValueAttribute {
// InvalidRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithNonArrayValueAttribute {
InvalidRepeatable value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface ContainerWithArrayValueAttributeButWrongComponentType {
String[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface InvalidRepeatable {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface PeteRepeats {
PeteRepeat[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(PeteRepeats.class)
@interface PeteRepeat {
String value();
}
@PeteRepeat("shadowed")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ForPetesSake {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeat("shadowed")
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ForTheLoveOfFoo {
@AliasFor(annotation = PeteRepeat.class)
String value();
}
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface ComposedContainer {
}
@PeteRepeat("A")
@PeteRepeats({ @PeteRepeat("B"), @PeteRepeat("C") })
static class RepeatableClass {
}
static class SubRepeatableClass extends RepeatableClass {
}
@ForPetesSake("B")
@ForTheLoveOfFoo("C")
@PeteRepeat("A")
static class ComposedRepeatableClass {
}
@ForPetesSake("C")
@PeteRepeats(@PeteRepeat("A"))
@PeteRepeat("B")
static class ComposedRepeatableMixedWithContainerClass {
}
@PeteRepeat("A")
@ComposedContainer
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 {
}
}