/*
* Copyright 2002-2017 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.Annotation;
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.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import static java.util.Arrays.*;
import static java.util.stream.Collectors.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* Unit tests for {@link AnnotationUtils}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
* @author Chris Beams
* @author Phillip Webb
* @author Oleg Zhurakousky
*/
public class AnnotationUtilsTests {
@Rule
public final ExpectedException exception = ExpectedException.none();
@Before
public void clearCachesBeforeTests() {
clearCaches();
}
static void clearCaches() {
clearCache("findAnnotationCache", "annotatedInterfaceCache", "metaPresentCache", "synthesizableCache",
"attributeAliasesCache", "attributeMethodsCache", "aliasDescriptorCache");
}
static void clearCache(String... cacheNames) {
stream(cacheNames).forEach(cacheName -> getCache(cacheName).clear());
}
static Map<?, ?> getCache(String cacheName) {
Field field = ReflectionUtils.findField(AnnotationUtils.class, cacheName);
ReflectionUtils.makeAccessible(field);
return (Map<?, ?>) ReflectionUtils.getField(field, null);
}
@Test
public void findMethodAnnotationOnLeaf() throws Exception {
Method m = Leaf.class.getMethod("annotatedOnLeaf");
assertNotNull(m.getAnnotation(Order.class));
assertNotNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class));
}
// @since 4.2
@Test
public void findMethodAnnotationWithAnnotationOnMethodInInterface() throws Exception {
Method m = Leaf.class.getMethod("fromInterfaceImplementedByRoot");
// @Order is not @Inherited
assertNull(m.getAnnotation(Order.class));
// getAnnotation() does not search on interfaces
assertNull(getAnnotation(m, Order.class));
// findAnnotation() does search on interfaces
assertNotNull(findAnnotation(m, Order.class));
}
// @since 4.2
@Test
public void findMethodAnnotationWithMetaAnnotationOnLeaf() throws Exception {
Method m = Leaf.class.getMethod("metaAnnotatedOnLeaf");
assertNull(m.getAnnotation(Order.class));
assertNotNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class));
}
// @since 4.2
@Test
public void findMethodAnnotationWithMetaMetaAnnotationOnLeaf() throws Exception {
Method m = Leaf.class.getMethod("metaMetaAnnotatedOnLeaf");
assertNull(m.getAnnotation(Component.class));
assertNull(getAnnotation(m, Component.class));
assertNotNull(findAnnotation(m, Component.class));
}
@Test
public void findMethodAnnotationOnRoot() throws Exception {
Method m = Leaf.class.getMethod("annotatedOnRoot");
assertNotNull(m.getAnnotation(Order.class));
assertNotNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class));
}
// @since 4.2
@Test
public void findMethodAnnotationWithMetaAnnotationOnRoot() throws Exception {
Method m = Leaf.class.getMethod("metaAnnotatedOnRoot");
assertNull(m.getAnnotation(Order.class));
assertNotNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class));
}
@Test
public void findMethodAnnotationOnRootButOverridden() throws Exception {
Method m = Leaf.class.getMethod("overrideWithoutNewAnnotation");
assertNull(m.getAnnotation(Order.class));
assertNull(getAnnotation(m, Order.class));
assertNotNull(findAnnotation(m, Order.class));
}
@Test
public void findMethodAnnotationNotAnnotated() throws Exception {
Method m = Leaf.class.getMethod("notAnnotated");
assertNull(findAnnotation(m, Order.class));
}
@Test
public void findMethodAnnotationOnBridgeMethod() throws Exception {
Method bridgeMethod = SimpleFoo.class.getMethod("something", Object.class);
assertTrue(bridgeMethod.isBridge());
assertNull(bridgeMethod.getAnnotation(Order.class));
assertNull(getAnnotation(bridgeMethod, Order.class));
assertNotNull(findAnnotation(bridgeMethod, Order.class));
// As of OpenJDK 8 b99, invoking getAnnotation() on a bridge method actually finds
// an annotation on its 'bridged' method. This differs from the previous behavior
// of JDK 5 through 7 and from the current behavior of the Eclipse compiler;
// however, we need to ensure that the tests pass in the Gradle build. So we
// comment out the following assertion.
// assertNull(bridgeMethod.getAnnotation(Transactional.class));
assertNotNull(getAnnotation(bridgeMethod, Transactional.class));
assertNotNull(findAnnotation(bridgeMethod, Transactional.class));
}
@Test
public void findMethodAnnotationOnBridgedMethod() throws Exception {
Method bridgedMethod = SimpleFoo.class.getMethod("something", String.class);
assertFalse(bridgedMethod.isBridge());
assertNull(bridgedMethod.getAnnotation(Order.class));
assertNull(getAnnotation(bridgedMethod, Order.class));
// AnnotationUtils.findAnnotation(Method, Class<A>) will not find an annotation on
// the bridge method for a bridged method.
assertNull(findAnnotation(bridgedMethod, Order.class));
assertNotNull(bridgedMethod.getAnnotation(Transactional.class));
assertNotNull(getAnnotation(bridgedMethod, Transactional.class));
assertNotNull(findAnnotation(bridgedMethod, Transactional.class));
}
@Test
public void findMethodAnnotationFromInterface() throws Exception {
Method method = ImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
Order order = findAnnotation(method, Order.class);
assertNotNull(order);
}
@Test
public void findMethodAnnotationFromInterfaceOnSuper() throws Exception {
Method method = SubOfImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
Order order = findAnnotation(method, Order.class);
assertNotNull(order);
}
@Test
public void findMethodAnnotationFromInterfaceWhenSuperDoesNotImplementMethod() throws Exception {
Method method = SubOfAbstractImplementsInterfaceWithAnnotatedMethod.class.getMethod("foo");
Order order = findAnnotation(method, Order.class);
assertNotNull(order);
}
/** @since 4.1.2 */
@Test
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
assertNotNull(component);
assertEquals("meta2", component.value());
}
/** @since 4.0.3 */
@Test
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedAnnotations() {
Transactional transactional = findAnnotation(SubSubClassWithInheritedAnnotation.class, Transactional.class);
assertNotNull(transactional);
assertTrue("readOnly flag for SubSubClassWithInheritedAnnotation", transactional.readOnly());
}
/** @since 4.0.3 */
@Test
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverInheritedComposedAnnotations() {
Component component = findAnnotation(SubSubClassWithInheritedMetaAnnotation.class, Component.class);
assertNotNull(component);
assertEquals("meta2", component.value());
}
@Test
public void findClassAnnotationOnMetaMetaAnnotatedClass() {
Component component = findAnnotation(MetaMetaAnnotatedClass.class, Component.class);
assertNotNull("Should find meta-annotation on composed annotation on class", component);
assertEquals("meta2", component.value());
}
@Test
public void findClassAnnotationOnMetaMetaMetaAnnotatedClass() {
Component component = findAnnotation(MetaMetaMetaAnnotatedClass.class, Component.class);
assertNotNull("Should find meta-annotation on meta-annotation on composed annotation on class", component);
assertEquals("meta2", component.value());
}
@Test
public void findClassAnnotationOnAnnotatedClassWithMissingTargetMetaAnnotation() {
// TransactionalClass is NOT annotated or meta-annotated with @Component
Component component = findAnnotation(TransactionalClass.class, Component.class);
assertNull("Should not find @Component on TransactionalClass", component);
}
@Test
public void findClassAnnotationOnMetaCycleAnnotatedClassWithMissingTargetMetaAnnotation() {
Component component = findAnnotation(MetaCycleAnnotatedClass.class, Component.class);
assertNull("Should not find @Component on MetaCycleAnnotatedClass", component);
}
// @since 4.2
@Test
public void findClassAnnotationOnInheritedAnnotationInterface() {
Transactional tx = findAnnotation(InheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on InheritedAnnotationInterface", tx);
}
// @since 4.2
@Test
public void findClassAnnotationOnSubInheritedAnnotationInterface() {
Transactional tx = findAnnotation(SubInheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on SubInheritedAnnotationInterface", tx);
}
// @since 4.2
@Test
public void findClassAnnotationOnSubSubInheritedAnnotationInterface() {
Transactional tx = findAnnotation(SubSubInheritedAnnotationInterface.class, Transactional.class);
assertNotNull("Should find @Transactional on SubSubInheritedAnnotationInterface", tx);
}
// @since 4.2
@Test
public void findClassAnnotationOnNonInheritedAnnotationInterface() {
Order order = findAnnotation(NonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on NonInheritedAnnotationInterface", order);
}
// @since 4.2
@Test
public void findClassAnnotationOnSubNonInheritedAnnotationInterface() {
Order order = findAnnotation(SubNonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on SubNonInheritedAnnotationInterface", order);
}
// @since 4.2
@Test
public void findClassAnnotationOnSubSubNonInheritedAnnotationInterface() {
Order order = findAnnotation(SubSubNonInheritedAnnotationInterface.class, Order.class);
assertNotNull("Should find @Order on SubSubNonInheritedAnnotationInterface", order);
}
@Test
public void findAnnotationDeclaringClassForAllScenarios() throws Exception {
// no class-level annotation
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClass() should still find it on classes.
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationClass.class));
}
@Test
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
// no class-level annotation
List<Class<? extends Annotation>> transactionalCandidateList = Collections.singletonList(Transactional.class);
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
List<Class<? extends Annotation>> orderCandidateList = Collections.singletonList(Order.class);
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationClass.class));
}
@Test
public void findAnnotationDeclaringClassForTypesWithMultipleCandidateTypes() {
List<Class<? extends Annotation>> candidates = asList(Transactional.class, Order.class);
// no class-level annotation
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationClass.class));
// class hierarchy mixed with @Transactional and @Order declarations
assertEquals(TransactionalClass.class,
findAnnotationDeclaringClassForTypes(candidates, TransactionalClass.class));
assertEquals(TransactionalAndOrderedClass.class,
findAnnotationDeclaringClassForTypes(candidates, TransactionalAndOrderedClass.class));
assertEquals(TransactionalAndOrderedClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubTransactionalAndOrderedClass.class));
}
@Test
public void isAnnotationDeclaredLocallyForAllScenarios() throws Exception {
// no class-level annotation
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedInterface.class));
assertFalse(isAnnotationDeclaredLocally(Transactional.class, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertTrue(isAnnotationDeclaredLocally(Transactional.class, InheritedAnnotationInterface.class));
assertFalse(isAnnotationDeclaredLocally(Transactional.class, SubInheritedAnnotationInterface.class));
assertTrue(isAnnotationDeclaredLocally(Transactional.class, InheritedAnnotationClass.class));
assertFalse(isAnnotationDeclaredLocally(Transactional.class, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited
assertTrue(isAnnotationDeclaredLocally(Order.class, NonInheritedAnnotationInterface.class));
assertFalse(isAnnotationDeclaredLocally(Order.class, SubNonInheritedAnnotationInterface.class));
assertTrue(isAnnotationDeclaredLocally(Order.class, NonInheritedAnnotationClass.class));
assertFalse(isAnnotationDeclaredLocally(Order.class, SubNonInheritedAnnotationClass.class));
}
@Test
public void isAnnotationInheritedForAllScenarios() throws Exception {
// no class-level annotation
assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedInterface.class));
assertFalse(isAnnotationInherited(Transactional.class, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertFalse(isAnnotationInherited(Transactional.class, InheritedAnnotationInterface.class));
// isAnnotationInherited() does not currently traverse interface hierarchies.
// Thus the following, though perhaps counter intuitive, must be false:
assertFalse(isAnnotationInherited(Transactional.class, SubInheritedAnnotationInterface.class));
assertFalse(isAnnotationInherited(Transactional.class, InheritedAnnotationClass.class));
assertTrue(isAnnotationInherited(Transactional.class, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited
assertFalse(isAnnotationInherited(Order.class, NonInheritedAnnotationInterface.class));
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationInterface.class));
assertFalse(isAnnotationInherited(Order.class, NonInheritedAnnotationClass.class));
assertFalse(isAnnotationInherited(Order.class, SubNonInheritedAnnotationClass.class));
}
@Test
public void getAnnotationAttributesWithoutAttributeAliases() {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(component);
assertNotNull(attributes);
assertEquals("value attribute: ", "webController", attributes.getString(VALUE));
assertEquals(Component.class, attributes.annotationType());
}
@Test
public void getAnnotationAttributesWithNestedAnnotations() {
ComponentScan componentScan = ComponentScanClass.class.getAnnotation(ComponentScan.class);
assertNotNull(componentScan);
AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanClass.class, componentScan);
assertNotNull(attributes);
assertEquals(ComponentScan.class, attributes.annotationType());
Filter[] filters = attributes.getAnnotationArray("excludeFilters", Filter.class);
assertNotNull(filters);
List<String> patterns = stream(filters).map(Filter::pattern).collect(toList());
assertEquals(asList("*Foo", "*Bar"), patterns);
}
@Test
public void getAnnotationAttributesWithAttributeAliases() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
AnnotationAttributes attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
assertNotNull(attributes);
assertEquals(WebMapping.class, attributes.annotationType());
assertEquals("name attribute: ", "foo", attributes.getString("name"));
assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE));
assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path"));
method = WebController.class.getMethod("handleMappedWithPathAttribute");
webMapping = method.getAnnotation(WebMapping.class);
attributes = (AnnotationAttributes) getAnnotationAttributes(webMapping);
assertNotNull(attributes);
assertEquals(WebMapping.class, attributes.annotationType());
assertEquals("name attribute: ", "bar", attributes.getString("name"));
assertArrayEquals("value attribute: ", asArray("/test"), attributes.getStringArray(VALUE));
assertArrayEquals("path attribute: ", asArray("/test"), attributes.getStringArray("path"));
}
@Test
public void getAnnotationAttributesWithAttributeAliasesWithDifferentValues() throws Exception {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
exception.expectMessage(containsString("values of [{/enigma}] and [{/test}]"));
Method method = WebController.class.getMethod("handleMappedWithDifferentPathAndValueAttributes");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
getAnnotationAttributes(webMapping);
}
@Test
public void getValueFromAnnotation() throws Exception {
Method method = SimpleFoo.class.getMethod("something", Object.class);
Order order = findAnnotation(method, Order.class);
assertEquals(1, getValue(order, VALUE));
assertEquals(1, getValue(order));
}
@Test
public void getValueFromNonPublicAnnotation() throws Exception {
Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations();
assertEquals(1, declaredAnnotations.length);
Annotation annotation = declaredAnnotations[0];
assertNotNull(annotation);
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
assertEquals(42, getValue(annotation, VALUE));
assertEquals(42, getValue(annotation));
}
@Test
public void getDefaultValueFromAnnotation() throws Exception {
Method method = SimpleFoo.class.getMethod("something", Object.class);
Order order = findAnnotation(method, Order.class);
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order, VALUE));
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(order));
}
@Test
public void getDefaultValueFromNonPublicAnnotation() throws Exception {
Annotation[] declaredAnnotations = NonPublicAnnotatedClass.class.getDeclaredAnnotations();
assertEquals(1, declaredAnnotations.length);
Annotation annotation = declaredAnnotations[0];
assertNotNull(annotation);
assertEquals("NonPublicAnnotation", annotation.annotationType().getSimpleName());
assertEquals(-1, getDefaultValue(annotation, VALUE));
assertEquals(-1, getDefaultValue(annotation));
}
@Test
public void getDefaultValueFromAnnotationType() throws Exception {
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class, VALUE));
assertEquals(Ordered.LOWEST_PRECEDENCE, getDefaultValue(Order.class));
}
@Test
public void findRepeatableAnnotationOnComposedAnnotation() {
Repeatable repeatable = findAnnotation(MyRepeatableMeta1.class, Repeatable.class);
assertNotNull(repeatable);
assertEquals(MyRepeatableContainer.class, repeatable.value());
}
@Test
public void getRepeatableAnnotationsDeclaredOnMethod() throws Exception {
Method method = InterfaceWithRepeated.class.getMethod("foo");
Set<MyRepeatable> annotations = getRepeatableAnnotations(method, MyRepeatable.class, MyRepeatableContainer.class);
assertNotNull(annotations);
List<String> values = annotations.stream().map(MyRepeatable::value).collect(toList());
assertThat(values, is(asList("A", "B", "C", "meta1")));
}
@Test
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute 'value' in"));
exception.expectMessage(containsString(BrokenContextConfig.class.getName()));
exception.expectMessage(containsString("@AliasFor [location]"));
getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class);
}
@Test
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception {
final List<String> expectedLocations = asList("A", "B");
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::location).collect(toList());
assertThat(locations, is(expectedLocations));
List<String> values = annotations.stream().map(ContextConfig::value).collect(toList());
assertThat(values, is(expectedLocations));
}
@Test
public void getRepeatableAnnotationsDeclaredOnClass() {
final List<String> expectedValuesJava = asList("A", "B", "C");
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
// Java 8
MyRepeatable[] array = MyRepeatableClass.class.getAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
assertThat(values, is(expectedValuesJava));
// Spring
Set<MyRepeatable> set = getRepeatableAnnotations(MyRepeatableClass.class, MyRepeatable.class, MyRepeatableContainer.class);
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
public void getRepeatableAnnotationsDeclaredOnSuperclass() {
final Class<?> clazz = SubMyRepeatableClass.class;
final List<String> expectedValuesJava = asList("A", "B", "C");
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
// Java 8
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
assertThat(values, is(expectedValuesJava));
// Spring
Set<MyRepeatable> set = getRepeatableAnnotations(clazz, MyRepeatable.class, MyRepeatableContainer.class);
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
public void getRepeatableAnnotationsDeclaredOnClassAndSuperclass() {
final Class<?> clazz = SubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
final List<String> expectedValuesJava = asList("X", "Y", "Z");
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
// Java 8
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
assertThat(values, is(expectedValuesJava));
// Spring
Set<MyRepeatable> set = getRepeatableAnnotations(clazz, MyRepeatable.class, MyRepeatableContainer.class);
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
public void getRepeatableAnnotationsDeclaredOnMultipleSuperclasses() {
final Class<?> clazz = SubSubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
final List<String> expectedValuesJava = asList("X", "Y", "Z");
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
// Java 8
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
assertThat(values, is(expectedValuesJava));
// Spring
Set<MyRepeatable> set = getRepeatableAnnotations(clazz, MyRepeatable.class, MyRepeatableContainer.class);
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
public void getDeclaredRepeatableAnnotationsDeclaredOnClass() {
final List<String> expectedValuesJava = asList("A", "B", "C");
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
// Java 8
MyRepeatable[] array = MyRepeatableClass.class.getDeclaredAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
List<String> values = stream(array).map(MyRepeatable::value).collect(toList());
assertThat(values, is(expectedValuesJava));
// Spring
Set<MyRepeatable> set = getDeclaredRepeatableAnnotations(MyRepeatableClass.class, MyRepeatable.class, MyRepeatableContainer.class);
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
public void getDeclaredRepeatableAnnotationsDeclaredOnSuperclass() {
final Class<?> clazz = SubMyRepeatableClass.class;
// Java 8
MyRepeatable[] array = clazz.getDeclaredAnnotationsByType(MyRepeatable.class);
assertNotNull(array);
assertThat(array.length, is(0));
// Spring
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
public void getAttributeOverrideNameFromWrongTargetAnnotation() throws Exception {
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
assertThat("xmlConfigFile is not an alias for @Component.",
getAttributeOverrideName(attribute, Component.class), is(nullValue()));
}
@Test
public void getAttributeOverrideNameForNonAliasedAttribute() throws Exception {
Method nonAliasedAttribute = ImplicitAliasesContextConfig.class.getDeclaredMethod("nonAliasedAttribute");
assertThat(getAttributeOverrideName(nonAliasedAttribute, ContextConfig.class), is(nullValue()));
}
@Test
public void getAttributeOverrideNameFromAliasedComposedAnnotation() throws Exception {
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
assertEquals("location", getAttributeOverrideName(attribute, ContextConfig.class));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithImplicitAliases() throws Exception {
Method xmlFile = ImplicitAliasesContextConfig.class.getDeclaredMethod("xmlFile");
Method groovyScript = ImplicitAliasesContextConfig.class.getDeclaredMethod("groovyScript");
Method value = ImplicitAliasesContextConfig.class.getDeclaredMethod("value");
Method location1 = ImplicitAliasesContextConfig.class.getDeclaredMethod("location1");
Method location2 = ImplicitAliasesContextConfig.class.getDeclaredMethod("location2");
Method location3 = ImplicitAliasesContextConfig.class.getDeclaredMethod("location3");
// Meta-annotation attribute overrides
assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(groovyScript, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(value, ContextConfig.class));
// Implicit aliases
assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "groovyScript", "location1", "location2", "location3"));
assertThat(getAttributeAliasNames(groovyScript), containsInAnyOrder("value", "xmlFile", "location1", "location2", "location3"));
assertThat(getAttributeAliasNames(value), containsInAnyOrder("xmlFile", "groovyScript", "location1", "location2", "location3"));
assertThat(getAttributeAliasNames(location1), containsInAnyOrder("xmlFile", "groovyScript", "value", "location2", "location3"));
assertThat(getAttributeAliasNames(location2), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location3"));
assertThat(getAttributeAliasNames(location3), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location2"));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithImplicitAliasesForAliasPair() throws Exception {
Method xmlFile = ImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("xmlFile");
Method groovyScript = ImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("groovyScript");
// Meta-annotation attribute overrides
assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class));
assertEquals("value", getAttributeOverrideName(groovyScript, ContextConfig.class));
// Implicit aliases
assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("groovyScript"));
assertThat(getAttributeAliasNames(groovyScript), containsInAnyOrder("xmlFile"));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted()
throws Exception {
Method value = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("value");
Method location = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("location");
Method xmlFile = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xmlFile");
// Meta-annotation attribute overrides
assertEquals("value", getAttributeOverrideName(value, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(location, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(xmlFile, ContextConfig.class));
// Implicit aliases
assertThat(getAttributeAliasNames(value), containsInAnyOrder("location", "xmlFile"));
assertThat(getAttributeAliasNames(location), containsInAnyOrder("value", "xmlFile"));
assertThat(getAttributeAliasNames(xmlFile), containsInAnyOrder("value", "location"));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliases() throws Exception {
Method xml = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("xml");
Method groovy = TransitiveImplicitAliasesContextConfig.class.getDeclaredMethod("groovy");
// Explicit meta-annotation attribute overrides
assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesContextConfig.class));
assertEquals("groovyScript", getAttributeOverrideName(groovy, ImplicitAliasesContextConfig.class));
// Transitive meta-annotation attribute overrides
assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class));
// Transitive implicit aliases
assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy"));
assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml"));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliasesForAliasPair() throws Exception {
Method xml = TransitiveImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("xml");
Method groovy = TransitiveImplicitAliasesForAliasPairContextConfig.class.getDeclaredMethod("groovy");
// Explicit meta-annotation attribute overrides
assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesForAliasPairContextConfig.class));
assertEquals("groovyScript", getAttributeOverrideName(groovy, ImplicitAliasesForAliasPairContextConfig.class));
// Transitive implicit aliases
assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy"));
assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml"));
}
@Test
public void getAttributeAliasNamesFromComposedAnnotationWithTransitiveImplicitAliasesWithImpliedAliasNamesOmitted()
throws Exception {
Method xml = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("xml");
Method groovy = TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class.getDeclaredMethod("groovy");
// Meta-annotation attribute overrides
assertEquals("location", getAttributeOverrideName(xml, ContextConfig.class));
assertEquals("location", getAttributeOverrideName(groovy, ContextConfig.class));
// Explicit meta-annotation attribute overrides
assertEquals("xmlFile", getAttributeOverrideName(xml, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class));
assertEquals("location", getAttributeOverrideName(groovy, ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class));
// Transitive implicit aliases
assertThat(getAttributeAliasNames(groovy), containsInAnyOrder("xml"));
assertThat(getAttributeAliasNames(xml), containsInAnyOrder("groovy"));
}
@Test
public void synthesizeAnnotationWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
Component synthesizedComponent = synthesizeAnnotation(component);
assertNotNull(synthesizedComponent);
assertSame(component, synthesizedComponent);
assertEquals("value attribute: ", "webController", synthesizedComponent.value());
}
@Test
public void synthesizeAnnotationsFromNullSources() throws Exception {
assertNull("null annotation", synthesizeAnnotation(null, null));
assertNull("null map", synthesizeAnnotation(null, WebMapping.class, null));
}
@Test
public void synthesizeAlreadySynthesizedAnnotation() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
assertNotNull(webMapping);
WebMapping synthesizedWebMapping = synthesizeAnnotation(webMapping);
assertNotSame(webMapping, synthesizedWebMapping);
WebMapping synthesizedAgainWebMapping = synthesizeAnnotation(synthesizedWebMapping);
assertThat(synthesizedAgainWebMapping, instanceOf(SynthesizedAnnotation.class));
assertSame(synthesizedWebMapping, synthesizedAgainWebMapping);
assertEquals("name attribute: ", "foo", synthesizedAgainWebMapping.name());
assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedAgainWebMapping.path());
assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedAgainWebMapping.value());
}
@Test
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("@AliasFor declaration on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
exception.expectMessage(containsString("points to itself"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName()));
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute 'foo' in"));
exception.expectMessage(containsString(AliasForNonexistentAttribute.class.getName()));
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasWithoutMirroredAliasFor() throws Exception {
AliasForWithoutMirroredAliasFor annotation =
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute 'bar' in"));
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
exception.expectMessage(containsString("@AliasFor [foo]"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasWithMirroredAliasForWrongAttribute() throws Exception {
AliasForWithMirroredAliasForWrongAttribute annotation =
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute 'bar' in"));
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'")));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForAttributeOfDifferentType() throws Exception {
AliasForAttributeOfDifferentType annotation =
AliasForAttributeOfDifferentTypeClass.class.getAnnotation(AliasForAttributeOfDifferentType.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
exception.expectMessage(containsString("attribute 'foo'"));
exception.expectMessage(containsString("attribute 'bar'"));
exception.expectMessage(containsString("same return type"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForWithMissingDefaultValues() throws Exception {
AliasForWithMissingDefaultValues annotation =
AliasForWithMissingDefaultValuesClass.class.getAnnotation(AliasForWithMissingDefaultValues.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
exception.expectMessage(containsString("attribute 'foo' in annotation"));
exception.expectMessage(containsString("attribute 'bar' in annotation"));
exception.expectMessage(containsString("default values"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForAttributeWithDifferentDefaultValue() throws Exception {
AliasForAttributeWithDifferentDefaultValue annotation =
AliasForAttributeWithDifferentDefaultValueClass.class.getAnnotation(AliasForAttributeWithDifferentDefaultValue.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
exception.expectMessage(containsString("attribute 'foo' in annotation"));
exception.expectMessage(containsString("attribute 'bar' in annotation"));
exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliasForMetaAnnotationThatIsNotMetaPresent() throws Exception {
AliasedComposedContextConfigNotMetaPresent annotation =
AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation"));
exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName()));
exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation"));
exception.expectMessage(containsString(ContextConfig.class.getName()));
exception.expectMessage(containsString("not meta-present"));
synthesizeAnnotation(annotation);
}
@Test
public void synthesizeAnnotationWithAttributeAliases() throws Exception {
Method method = WebController.class.getMethod("handleMappedWithValueAttribute");
WebMapping webMapping = method.getAnnotation(WebMapping.class);
assertNotNull(webMapping);
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMapping);
assertThat(synthesizedWebMapping1, instanceOf(SynthesizedAnnotation.class));
assertNotSame(webMapping, synthesizedWebMapping1);
assertEquals("name attribute: ", "foo", synthesizedWebMapping1.name());
assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping1.path());
assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping1.value());
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMapping);
assertThat(synthesizedWebMapping2, instanceOf(SynthesizedAnnotation.class));
assertNotSame(webMapping, synthesizedWebMapping2);
assertEquals("name attribute: ", "foo", synthesizedWebMapping2.name());
assertArrayEquals("aliased path attribute: ", asArray("/test"), synthesizedWebMapping2.path());
assertArrayEquals("actual value attribute: ", asArray("/test"), synthesizedWebMapping2.value());
}
@Test
public void synthesizeAnnotationWithImplicitAliases() throws Exception {
assertAnnotationSynthesisWithImplicitAliases(ValueImplicitAliasesContextConfigClass.class, "value");
assertAnnotationSynthesisWithImplicitAliases(Location1ImplicitAliasesContextConfigClass.class, "location1");
assertAnnotationSynthesisWithImplicitAliases(XmlImplicitAliasesContextConfigClass.class, "xmlFile");
assertAnnotationSynthesisWithImplicitAliases(GroovyImplicitAliasesContextConfigClass.class, "groovyScript");
}
private void assertAnnotationSynthesisWithImplicitAliases(Class<?> clazz, String expected) throws Exception {
ImplicitAliasesContextConfig config = clazz.getAnnotation(ImplicitAliasesContextConfig.class);
assertNotNull(config);
ImplicitAliasesContextConfig synthesizedConfig = synthesizeAnnotation(config);
assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
assertEquals("value: ", expected, synthesizedConfig.value());
assertEquals("location1: ", expected, synthesizedConfig.location1());
assertEquals("xmlFile: ", expected, synthesizedConfig.xmlFile());
assertEquals("groovyScript: ", expected, synthesizedConfig.groovyScript());
}
@Test
public void synthesizeAnnotationWithImplicitAliasesWithImpliedAliasNamesOmitted() throws Exception {
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "value");
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "location");
assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(
XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass.class, "xmlFile");
}
private void assertAnnotationSynthesisWithImplicitAliasesWithImpliedAliasNamesOmitted(Class<?> clazz,
String expected) throws Exception {
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig config = clazz.getAnnotation(
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class);
assertNotNull(config);
ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig synthesizedConfig = synthesizeAnnotation(config);
assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
assertEquals("value: ", expected, synthesizedConfig.value());
assertEquals("locations: ", expected, synthesizedConfig.location());
assertEquals("xmlFiles: ", expected, synthesizedConfig.xmlFile());
}
@Test
public void synthesizeAnnotationWithImplicitAliasesForAliasPair() throws Exception {
Class<?> clazz = ImplicitAliasesForAliasPairContextConfigClass.class;
ImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(ImplicitAliasesForAliasPairContextConfig.class);
assertNotNull(config);
ImplicitAliasesForAliasPairContextConfig synthesizedConfig = synthesizeAnnotation(config);
assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
assertEquals("xmlFile: ", "test.xml", synthesizedConfig.xmlFile());
assertEquals("groovyScript: ", "test.xml", synthesizedConfig.groovyScript());
}
@Test
public void synthesizeAnnotationWithTransitiveImplicitAliases() throws Exception {
Class<?> clazz = TransitiveImplicitAliasesContextConfigClass.class;
TransitiveImplicitAliasesContextConfig config = clazz.getAnnotation(TransitiveImplicitAliasesContextConfig.class);
assertNotNull(config);
TransitiveImplicitAliasesContextConfig synthesizedConfig = synthesizeAnnotation(config);
assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
assertEquals("xml: ", "test.xml", synthesizedConfig.xml());
assertEquals("groovy: ", "test.xml", synthesizedConfig.groovy());
}
@Test
public void synthesizeAnnotationWithTransitiveImplicitAliasesForAliasPair() throws Exception {
Class<?> clazz = TransitiveImplicitAliasesForAliasPairContextConfigClass.class;
TransitiveImplicitAliasesForAliasPairContextConfig config = clazz.getAnnotation(TransitiveImplicitAliasesForAliasPairContextConfig.class);
assertNotNull(config);
TransitiveImplicitAliasesForAliasPairContextConfig synthesizedConfig = synthesizeAnnotation(config);
assertThat(synthesizedConfig, instanceOf(SynthesizedAnnotation.class));
assertEquals("xml: ", "test.xml", synthesizedConfig.xml());
assertEquals("groovy: ", "test.xml", synthesizedConfig.groovy());
}
@Test
public void synthesizeAnnotationWithImplicitAliasesWithMissingDefaultValues() throws Exception {
Class<?> clazz = ImplicitAliasesWithMissingDefaultValuesContextConfigClass.class;
Class<ImplicitAliasesWithMissingDefaultValuesContextConfig> annotationType = ImplicitAliasesWithMissingDefaultValuesContextConfig.class;
ImplicitAliasesWithMissingDefaultValuesContextConfig config = clazz.getAnnotation(annotationType);
assertNotNull(config);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("default values"));
synthesizeAnnotation(config, clazz);
}
@Test
public void synthesizeAnnotationWithImplicitAliasesWithDifferentDefaultValues() throws Exception {
Class<?> clazz = ImplicitAliasesWithDifferentDefaultValuesContextConfigClass.class;
Class<ImplicitAliasesWithDifferentDefaultValuesContextConfig> annotationType = ImplicitAliasesWithDifferentDefaultValuesContextConfig.class;
ImplicitAliasesWithDifferentDefaultValuesContextConfig config = clazz.getAnnotation(annotationType);
assertNotNull(config);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(config, clazz);
}
@Test
public void synthesizeAnnotationWithImplicitAliasesWithDuplicateValues() throws Exception {
Class<?> clazz = ImplicitAliasesWithDuplicateValuesContextConfigClass.class;
Class<ImplicitAliasesWithDuplicateValuesContextConfig> annotationType = ImplicitAliasesWithDuplicateValuesContextConfig.class;
ImplicitAliasesWithDuplicateValuesContextConfig config = clazz.getAnnotation(annotationType);
assertNotNull(config);
ImplicitAliasesWithDuplicateValuesContextConfig synthesizedConfig = synthesizeAnnotation(config, clazz);
assertNotNull(synthesizedConfig);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("In annotation"));
exception.expectMessage(containsString(annotationType.getName()));
exception.expectMessage(containsString("declared on class"));
exception.expectMessage(containsString(clazz.getName()));
exception.expectMessage(containsString("and synthesized from"));
exception.expectMessage(either(containsString("attribute 'location1' and its alias 'location2'")).or(
containsString("attribute 'location2' and its alias 'location1'")));
exception.expectMessage(either(containsString("are present with values of [1] and [2]")).or(
containsString("are present with values of [2] and [1]")));
synthesizedConfig.location1();
}
@Test
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
Map<String, Object> map = Collections.singletonMap(VALUE, "webController");
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
assertNotSame(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test
@SuppressWarnings("unchecked")
public void synthesizeAnnotationFromMapWithNestedMap() throws Exception {
ComponentScanSingleFilter componentScan = ComponentScanSingleFilterClass.class.getAnnotation(ComponentScanSingleFilter.class);
assertNotNull(componentScan);
assertEquals("value from ComponentScan: ", "*Foo", componentScan.value().pattern());
AnnotationAttributes attributes = getAnnotationAttributes(
ComponentScanSingleFilterClass.class, componentScan, false, true);
assertNotNull(attributes);
assertEquals(ComponentScanSingleFilter.class, attributes.annotationType());
Map<String, Object> filterMap = (Map<String, Object>) attributes.get("value");
assertNotNull(filterMap);
assertEquals("*Foo", filterMap.get("pattern"));
// Modify nested map
filterMap.put("pattern", "newFoo");
filterMap.put("enigma", 42);
ComponentScanSingleFilter synthesizedComponentScan = synthesizeAnnotation(
attributes, ComponentScanSingleFilter.class, ComponentScanSingleFilterClass.class);
assertNotNull(synthesizedComponentScan);
assertNotSame(componentScan, synthesizedComponentScan);
assertEquals("value from synthesized ComponentScan: ", "newFoo", synthesizedComponentScan.value().pattern());
}
@Test
@SuppressWarnings("unchecked")
public void synthesizeAnnotationFromMapWithNestedArrayOfMaps() throws Exception {
ComponentScan componentScan = ComponentScanClass.class.getAnnotation(ComponentScan.class);
assertNotNull(componentScan);
AnnotationAttributes attributes = getAnnotationAttributes(ComponentScanClass.class, componentScan, false, true);
assertNotNull(attributes);
assertEquals(ComponentScan.class, attributes.annotationType());
Map<String, Object>[] filters = (Map[]) attributes.get("excludeFilters");
assertNotNull(filters);
List<String> patterns = stream(filters).map(m -> (String) m.get("pattern")).collect(toList());
assertEquals(asList("*Foo", "*Bar"), patterns);
// Modify nested maps
filters[0].put("pattern", "newFoo");
filters[0].put("enigma", 42);
filters[1].put("pattern", "newBar");
filters[1].put("enigma", 42);
ComponentScan synthesizedComponentScan = synthesizeAnnotation(attributes, ComponentScan.class, ComponentScanClass.class);
assertNotNull(synthesizedComponentScan);
assertNotSame(componentScan, synthesizedComponentScan);
patterns = stream(synthesizedComponentScan.excludeFilters()).map(Filter::pattern).collect(toList());
assertEquals(asList("newFoo", "newBar"), patterns);
}
@Test
public void synthesizeAnnotationFromDefaultsWithoutAttributeAliases() throws Exception {
AnnotationWithDefaults annotationWithDefaults = synthesizeAnnotation(AnnotationWithDefaults.class);
assertNotNull(annotationWithDefaults);
assertEquals("text: ", "enigma", annotationWithDefaults.text());
assertTrue("predicate: ", annotationWithDefaults.predicate());
assertArrayEquals("characters: ", new char[] { 'a', 'b', 'c' }, annotationWithDefaults.characters());
}
@Test
public void synthesizeAnnotationFromDefaultsWithAttributeAliases() throws Exception {
ContextConfig contextConfig = synthesizeAnnotation(ContextConfig.class);
assertNotNull(contextConfig);
assertEquals("value: ", "", contextConfig.value());
assertEquals("location: ", "", contextConfig.location());
}
@Test
public void synthesizeAnnotationWithAttributeAliasesWithDifferentValues() throws Exception {
ContextConfig contextConfig = synthesizeAnnotation(ContextConfigMismatch.class.getAnnotation(ContextConfig.class));
exception.expect(AnnotationConfigurationException.class);
getValue(contextConfig);
}
@Test
public void synthesizeAnnotationFromMapWithMinimalAttributesWithAttributeAliases() throws Exception {
Map<String, Object> map = Collections.singletonMap("location", "test.xml");
ContextConfig contextConfig = synthesizeAnnotation(map, ContextConfig.class, null);
assertNotNull(contextConfig);
assertEquals("value: ", "test.xml", contextConfig.value());
assertEquals("location: ", "test.xml", contextConfig.location());
}
@Test
public void synthesizeAnnotationFromMapWithAttributeAliasesThatOverrideArraysWithSingleElements() throws Exception {
Map<String, Object> map = Collections.singletonMap("value", "/foo");
Get get = synthesizeAnnotation(map, Get.class, null);
assertNotNull(get);
assertEquals("value: ", "/foo", get.value());
assertEquals("path: ", "/foo", get.path());
map = Collections.singletonMap("path", "/foo");
get = synthesizeAnnotation(map, Get.class, null);
assertNotNull(get);
assertEquals("value: ", "/foo", get.value());
assertEquals("path: ", "/foo", get.path());
}
@Test
public void synthesizeAnnotationFromMapWithImplicitAttributeAliases() throws Exception {
assertAnnotationSynthesisFromMapWithImplicitAliases("value");
assertAnnotationSynthesisFromMapWithImplicitAliases("location1");
assertAnnotationSynthesisFromMapWithImplicitAliases("location2");
assertAnnotationSynthesisFromMapWithImplicitAliases("location3");
assertAnnotationSynthesisFromMapWithImplicitAliases("xmlFile");
assertAnnotationSynthesisFromMapWithImplicitAliases("groovyScript");
}
private void assertAnnotationSynthesisFromMapWithImplicitAliases(String attributeNameAndValue) throws Exception {
Map<String, Object> map = Collections.singletonMap(attributeNameAndValue, attributeNameAndValue);
ImplicitAliasesContextConfig config = synthesizeAnnotation(map, ImplicitAliasesContextConfig.class, null);
assertNotNull(config);
assertEquals("value: ", attributeNameAndValue, config.value());
assertEquals("location1: ", attributeNameAndValue, config.location1());
assertEquals("location2: ", attributeNameAndValue, config.location2());
assertEquals("location3: ", attributeNameAndValue, config.location3());
assertEquals("xmlFile: ", attributeNameAndValue, config.xmlFile());
assertEquals("groovyScript: ", attributeNameAndValue, config.groovyScript());
}
@Test
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
assertMissingTextAttribute(Collections.emptyMap());
}
@Test
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
Map<String, Object> map = Collections.singletonMap("text", null);
assertTrue(map.containsKey("text"));
assertMissingTextAttribute(map);
}
private void assertMissingTextAttribute(Map<String, Object> attributes) {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute 'text'"));
exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"));
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
}
@Test
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned a value of type [java.lang.Long]"));
exception.expectMessage(containsString("for attribute 'value'"));
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
synthesizeAnnotation(map, Component.class, null);
}
@Test
public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception {
// 1) Get an annotation
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);
// 2) Convert the annotation into AnnotationAttributes
AnnotationAttributes attributes = getAnnotationAttributes(WebController.class, component);
assertNotNull(attributes);
// 3) Synthesize the AnnotationAttributes back into an annotation
Component synthesizedComponent = synthesizeAnnotation(attributes, Component.class, WebController.class);
assertNotNull(synthesizedComponent);
// 4) Verify that the original and synthesized annotations are equivalent
assertNotSame(component, synthesizedComponent);
assertEquals(component, synthesizedComponent);
assertEquals("value from component: ", "webController", component.value());
assertEquals("value from synthesized component: ", "webController", synthesizedComponent.value());
}
@Test
public void toStringForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithAliases);
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithPathAndValue);
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping1);
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping2);
assertThat(webMappingWithAliases.toString(), is(not(synthesizedWebMapping1.toString())));
assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping1);
assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping2);
}
private void assertToStringForWebMappingWithPathAndValue(WebMapping webMapping) {
String string = webMapping.toString();
assertThat(string, startsWith("@" + WebMapping.class.getName() + "("));
assertThat(string, containsString("value=[/test]"));
assertThat(string, containsString("path=[/test]"));
assertThat(string, containsString("name=bar"));
assertThat(string, containsString("method="));
assertThat(string, containsString("[GET, POST]"));
assertThat(string, endsWith(")"));
}
@Test
public void equalsForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithAliases);
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithPathAndValue);
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping1);
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping2);
// Equality amongst standard annotations
assertThat(webMappingWithAliases, is(webMappingWithAliases));
assertThat(webMappingWithPathAndValue, is(webMappingWithPathAndValue));
// Inequality amongst standard annotations
assertThat(webMappingWithAliases, is(not(webMappingWithPathAndValue)));
assertThat(webMappingWithPathAndValue, is(not(webMappingWithAliases)));
// Equality amongst synthesized annotations
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping1));
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping2));
assertThat(synthesizedWebMapping1, is(synthesizedWebMapping2));
assertThat(synthesizedWebMapping2, is(synthesizedWebMapping1));
// Equality between standard and synthesized annotations
assertThat(synthesizedWebMapping1, is(webMappingWithPathAndValue));
assertThat(webMappingWithPathAndValue, is(synthesizedWebMapping1));
// Inequality between standard and synthesized annotations
assertThat(synthesizedWebMapping1, is(not(webMappingWithAliases)));
assertThat(webMappingWithAliases, is(not(synthesizedWebMapping1)));
}
@Test
public void hashCodeForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
WebMapping webMappingWithAliases = methodWithPath.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithAliases);
Method methodWithPathAndValue = WebController.class.getMethod("handleMappedWithSamePathAndValueAttributes");
WebMapping webMappingWithPathAndValue = methodWithPathAndValue.getAnnotation(WebMapping.class);
assertNotNull(webMappingWithPathAndValue);
WebMapping synthesizedWebMapping1 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping1);
WebMapping synthesizedWebMapping2 = synthesizeAnnotation(webMappingWithAliases);
assertNotNull(synthesizedWebMapping2);
// Equality amongst standard annotations
assertThat(webMappingWithAliases.hashCode(), is(webMappingWithAliases.hashCode()));
assertThat(webMappingWithPathAndValue.hashCode(), is(webMappingWithPathAndValue.hashCode()));
// Inequality amongst standard annotations
assertThat(webMappingWithAliases.hashCode(), is(not(webMappingWithPathAndValue.hashCode())));
assertThat(webMappingWithPathAndValue.hashCode(), is(not(webMappingWithAliases.hashCode())));
// Equality amongst synthesized annotations
assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping1.hashCode()));
assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping2.hashCode()));
assertThat(synthesizedWebMapping1.hashCode(), is(synthesizedWebMapping2.hashCode()));
assertThat(synthesizedWebMapping2.hashCode(), is(synthesizedWebMapping1.hashCode()));
// Equality between standard and synthesized annotations
assertThat(synthesizedWebMapping1.hashCode(), is(webMappingWithPathAndValue.hashCode()));
assertThat(webMappingWithPathAndValue.hashCode(), is(synthesizedWebMapping1.hashCode()));
// Inequality between standard and synthesized annotations
assertThat(synthesizedWebMapping1.hashCode(), is(not(webMappingWithAliases.hashCode())));
assertThat(webMappingWithAliases.hashCode(), is(not(synthesizedWebMapping1.hashCode())));
}
/**
* Fully reflection-based test that verifies support for
* {@linkplain AnnotationUtils#synthesizeAnnotation synthesizing annotations}
* across packages with non-public visibility of user types (e.g., a non-public
* annotation that uses {@code @AliasFor}).
*/
@Test
@SuppressWarnings("unchecked")
public void synthesizeNonPublicAnnotationWithAttributeAliasesFromDifferentPackage() throws Exception {
Class<?> clazz =
ClassUtils.forName("org.springframework.core.annotation.subpackage.NonPublicAliasedAnnotatedClass", null);
Class<? extends Annotation> annotationType = (Class<? extends Annotation>)
ClassUtils.forName("org.springframework.core.annotation.subpackage.NonPublicAliasedAnnotation", null);
Annotation annotation = clazz.getAnnotation(annotationType);
assertNotNull(annotation);
Annotation synthesizedAnnotation = synthesizeAnnotation(annotation);
assertNotSame(annotation, synthesizedAnnotation);
assertNotNull(synthesizedAnnotation);
assertEquals("name attribute: ", "test", getValue(synthesizedAnnotation, "name"));
assertEquals("aliased path attribute: ", "/test", getValue(synthesizedAnnotation, "path"));
assertEquals("aliased path attribute: ", "/test", getValue(synthesizedAnnotation, "value"));
}
@Test
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
List<String> expectedLocations = asList("A", "B");
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
assertNotNull(hierarchy);
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
assertNotSame(hierarchy, synthesizedHierarchy);
assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
ContextConfig[] configs = synthesizedHierarchy.value();
assertNotNull(configs);
assertTrue("nested annotations must be synthesized",
stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
assertThat(locations, is(expectedLocations));
List<String> values = stream(configs).map(ContextConfig::value).collect(toList());
assertThat(values, is(expectedLocations));
}
@Test
public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
List<String> expectedLocations = asList("A", "B");
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
assertNotNull(hierarchy);
Hierarchy synthesizedHierarchy = synthesizeAnnotation(hierarchy);
assertThat(synthesizedHierarchy, instanceOf(SynthesizedAnnotation.class));
ContextConfig contextConfig = SimpleConfigTestCase.class.getAnnotation(ContextConfig.class);
assertNotNull(contextConfig);
ContextConfig[] configs = synthesizedHierarchy.value();
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
assertThat(locations, is(expectedLocations));
// Alter array returned from synthesized annotation
configs[0] = contextConfig;
// Re-retrieve the array from the synthesized annotation
configs = synthesizedHierarchy.value();
List<String> values = stream(configs).map(ContextConfig::value).collect(toList());
assertThat(values, is(expectedLocations));
}
@Test
public void synthesizeAnnotationWithArrayOfChars() throws Exception {
CharsContainer charsContainer = GroupOfCharsClass.class.getAnnotation(CharsContainer.class);
assertNotNull(charsContainer);
CharsContainer synthesizedCharsContainer = synthesizeAnnotation(charsContainer);
assertThat(synthesizedCharsContainer, instanceOf(SynthesizedAnnotation.class));
char[] chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
// Alter array returned from synthesized annotation
chars[0] = '?';
// Re-retrieve the array from the synthesized annotation
chars = synthesizedCharsContainer.chars();
assertArrayEquals(new char[] { 'x', 'y', 'z' }, chars);
}
@SafeVarargs
static <T> T[] asArray(T... arr) {
return arr;
}
@Component("meta1")
@Order
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Meta1 {
}
@Component("meta2")
@Transactional(readOnly = true)
@Retention(RetentionPolicy.RUNTIME)
@interface Meta2 {
}
@Meta2
@Retention(RetentionPolicy.RUNTIME)
@interface MetaMeta {
}
@MetaMeta
@Retention(RetentionPolicy.RUNTIME)
@interface MetaMetaMeta {
}
@MetaCycle3
@Retention(RetentionPolicy.RUNTIME)
@interface MetaCycle1 {
}
@MetaCycle1
@Retention(RetentionPolicy.RUNTIME)
@interface MetaCycle2 {
}
@MetaCycle2
@Retention(RetentionPolicy.RUNTIME)
@interface MetaCycle3 {
}
@Meta1
interface InterfaceWithMetaAnnotation {
}
@Meta2
static class ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface implements InterfaceWithMetaAnnotation {
}
@Meta1
static class ClassWithInheritedMetaAnnotation {
}
@Meta2
static class SubClassWithInheritedMetaAnnotation extends ClassWithInheritedMetaAnnotation {
}
static class SubSubClassWithInheritedMetaAnnotation extends SubClassWithInheritedMetaAnnotation {
}
@Transactional
static class ClassWithInheritedAnnotation {
}
@Meta2
static class SubClassWithInheritedAnnotation extends ClassWithInheritedAnnotation {
}
static class SubSubClassWithInheritedAnnotation extends SubClassWithInheritedAnnotation {
}
@MetaMeta
static class MetaMetaAnnotatedClass {
}
@MetaMetaMeta
static class MetaMetaMetaAnnotatedClass {
}
@MetaCycle3
static class MetaCycleAnnotatedClass {
}
public interface AnnotatedInterface {
@Order(0)
void fromInterfaceImplementedByRoot();
}
public static class Root implements AnnotatedInterface {
@Order(27)
public void annotatedOnRoot() {
}
@Meta1
public void metaAnnotatedOnRoot() {
}
public void overrideToAnnotate() {
}
@Order(27)
public void overrideWithoutNewAnnotation() {
}
public void notAnnotated() {
}
@Override
public void fromInterfaceImplementedByRoot() {
}
}
public static class Leaf extends Root {
@Order(25)
public void annotatedOnLeaf() {
}
@Meta1
public void metaAnnotatedOnLeaf() {
}
@MetaMeta
public void metaMetaAnnotatedOnLeaf() {
}
@Override
@Order(1)
public void overrideToAnnotate() {
}
@Override
public void overrideWithoutNewAnnotation() {
}
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface Transactional {
boolean readOnly() default false;
}
public static abstract class Foo<T> {
@Order(1)
public abstract void something(T arg);
}
public static class SimpleFoo extends Foo<String> {
@Override
@Transactional
public void something(final String arg) {
}
}
@Transactional
public interface InheritedAnnotationInterface {
}
public interface SubInheritedAnnotationInterface extends InheritedAnnotationInterface {
}
public interface SubSubInheritedAnnotationInterface extends SubInheritedAnnotationInterface {
}
@Order
public interface NonInheritedAnnotationInterface {
}
public interface SubNonInheritedAnnotationInterface extends NonInheritedAnnotationInterface {
}
public interface SubSubNonInheritedAnnotationInterface extends SubNonInheritedAnnotationInterface {
}
public static class NonAnnotatedClass {
}
public interface NonAnnotatedInterface {
}
@Transactional
public static class InheritedAnnotationClass {
}
public static class SubInheritedAnnotationClass extends InheritedAnnotationClass {
}
@Order
public static class NonInheritedAnnotationClass {
}
public static class SubNonInheritedAnnotationClass extends NonInheritedAnnotationClass {
}
@Transactional
public static class TransactionalClass {
}
@Order
public static class TransactionalAndOrderedClass extends TransactionalClass {
}
public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
}
public interface InterfaceWithAnnotatedMethod {
@Order
void foo();
}
public static class ImplementsInterfaceWithAnnotatedMethod implements InterfaceWithAnnotatedMethod {
@Override
public void foo() {
}
}
public static class SubOfImplementsInterfaceWithAnnotatedMethod extends ImplementsInterfaceWithAnnotatedMethod {
@Override
public void foo() {
}
}
public abstract static class AbstractDoesNotImplementInterfaceWithAnnotatedMethod
implements InterfaceWithAnnotatedMethod {
}
public static class SubOfAbstractImplementsInterfaceWithAnnotatedMethod
extends AbstractDoesNotImplementInterfaceWithAnnotatedMethod {
@Override
public void foo() {
}
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface MyRepeatableContainer {
MyRepeatable[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Repeatable(MyRepeatableContainer.class)
@interface MyRepeatable {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MyRepeatable("meta1")
@interface MyRepeatableMeta1 {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MyRepeatable("meta2")
@interface MyRepeatableMeta2 {
}
interface InterfaceWithRepeated {
@MyRepeatable("A")
@MyRepeatableContainer({@MyRepeatable("B"), @MyRepeatable("C")})
@MyRepeatableMeta1
void foo();
}
@MyRepeatable("A")
@MyRepeatableContainer({@MyRepeatable("B"), @MyRepeatable("C")})
@MyRepeatableMeta1
static class MyRepeatableClass {
}
static class SubMyRepeatableClass extends MyRepeatableClass {
}
@MyRepeatable("X")
@MyRepeatableContainer({@MyRepeatable("Y"), @MyRepeatable("Z")})
@MyRepeatableMeta2
static class SubMyRepeatableWithAdditionalLocalDeclarationsClass extends MyRepeatableClass {
}
static class SubSubMyRepeatableWithAdditionalLocalDeclarationsClass extends
SubMyRepeatableWithAdditionalLocalDeclarationsClass {
}
enum RequestMethod {
GET, POST
}
/**
* Mock of {@code org.springframework.web.bind.annotation.RequestMapping}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface WebMapping {
String name();
@AliasFor("path")
String[] value() default "";
@AliasFor(attribute = "value")
String[] path() default "";
RequestMethod[] method() default {};
}
/**
* Mock of {@code org.springframework.web.bind.annotation.GetMapping}, except
* that the String arrays are overridden with single String elements.
*/
@Retention(RetentionPolicy.RUNTIME)
@WebMapping(method = RequestMethod.GET, name = "")
@interface Get {
@AliasFor(annotation = WebMapping.class)
String value() default "";
@AliasFor(annotation = WebMapping.class)
String path() default "";
}
/**
* Mock of {@code org.springframework.web.bind.annotation.PostMapping}, except
* that the path is overridden by convention with single String element.
*/
@Retention(RetentionPolicy.RUNTIME)
@WebMapping(method = RequestMethod.POST, name = "")
@interface Post {
String path() default "";
}
@Component("webController")
static class WebController {
@WebMapping(value = "/test", name = "foo")
public void handleMappedWithValueAttribute() {
}
@WebMapping(path = "/test", name = "bar", method = { RequestMethod.GET, RequestMethod.POST })
public void handleMappedWithPathAttribute() {
}
@Get("/test")
public void getMappedWithValueAttribute() {
}
@Get(path = "/test")
public void getMappedWithPathAttribute() {
}
@Post(path = "/test")
public void postMappedWithPathAttribute() {
}
/**
* mapping is logically "equal" to handleMappedWithPathAttribute().
*/
@WebMapping(value = "/test", path = "/test", name = "bar", method = { RequestMethod.GET, RequestMethod.POST })
public void handleMappedWithSamePathAndValueAttributes() {
}
@WebMapping(value = "/enigma", path = "/test", name = "baz")
public void handleMappedWithDifferentPathAndValueAttributes() {
}
}
/**
* Mock of {@code org.springframework.test.context.ContextConfiguration}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ContextConfig {
@AliasFor("location")
String value() default "";
@AliasFor("value")
String location() default "";
Class<?> klass() default Object.class;
}
@Retention(RetentionPolicy.RUNTIME)
@interface BrokenContextConfig {
// Intentionally missing:
// @AliasFor("location")
String value() default "";
@AliasFor("value")
String location() default "";
}
/**
* Mock of {@code org.springframework.test.context.ContextHierarchy}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Hierarchy {
ContextConfig[] value();
}
@Retention(RetentionPolicy.RUNTIME)
@interface BrokenHierarchy {
BrokenContextConfig[] value();
}
@Hierarchy({@ContextConfig("A"), @ContextConfig(location = "B")})
static class ConfigHierarchyTestCase {
}
@BrokenHierarchy(@BrokenContextConfig)
static class BrokenConfigHierarchyTestCase {
}
@ContextConfig("simple.xml")
static class SimpleConfigTestCase {
}
@Retention(RetentionPolicy.RUNTIME)
@interface CharsContainer {
@AliasFor(attribute = "chars")
char[] value() default {};
@AliasFor(attribute = "value")
char[] chars() default {};
}
@CharsContainer(chars = { 'x', 'y', 'z' })
static class GroupOfCharsClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForWithMissingAttributeDeclaration {
@AliasFor
String foo() default "";
}
@AliasForWithMissingAttributeDeclaration
static class AliasForWithMissingAttributeDeclarationClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForWithDuplicateAttributeDeclaration {
@AliasFor(value = "bar", attribute = "baz")
String foo() default "";
}
@AliasForWithDuplicateAttributeDeclaration
static class AliasForWithDuplicateAttributeDeclarationClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForNonexistentAttribute {
@AliasFor("bar")
String foo() default "";
}
@AliasForNonexistentAttribute
static class AliasForNonexistentAttributeClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForWithoutMirroredAliasFor {
@AliasFor("bar")
String foo() default "";
String bar() default "";
}
@AliasForWithoutMirroredAliasFor
static class AliasForWithoutMirroredAliasForClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForWithMirroredAliasForWrongAttribute {
@AliasFor(attribute = "bar")
String[] foo() default "";
@AliasFor(attribute = "quux")
String[] bar() default "";
}
@AliasForWithMirroredAliasForWrongAttribute
static class AliasForWithMirroredAliasForWrongAttributeClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForAttributeOfDifferentType {
@AliasFor("bar")
String[] foo() default "";
@AliasFor("foo")
boolean bar() default true;
}
@AliasForAttributeOfDifferentType
static class AliasForAttributeOfDifferentTypeClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForWithMissingDefaultValues {
@AliasFor(attribute = "bar")
String foo();
@AliasFor(attribute = "foo")
String bar();
}
@AliasForWithMissingDefaultValues(foo = "foo", bar = "bar")
static class AliasForWithMissingDefaultValuesClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AliasForAttributeWithDifferentDefaultValue {
@AliasFor("bar")
String foo() default "X";
@AliasFor("foo")
String bar() default "Z";
}
@AliasForAttributeWithDifferentDefaultValue
static class AliasForAttributeWithDifferentDefaultValueClass {
}
// @ContextConfig --> Intentionally NOT meta-present
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedComposedContextConfigNotMetaPresent {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlConfigFile();
}
@AliasedComposedContextConfigNotMetaPresent(xmlConfigFile = "test.xml")
static class AliasedComposedContextConfigNotMetaPresentClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface AliasedComposedContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlConfigFile();
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
public @interface ImplicitAliasesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlFile() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String groovyScript() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String value() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location1() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location2() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location3() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "klass")
Class<?> configClass() default Object.class;
String nonAliasedAttribute() default "";
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig(groovyScript = "groovyScript")
static class GroovyImplicitAliasesContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig(xmlFile = "xmlFile")
static class XmlImplicitAliasesContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig("value")
static class ValueImplicitAliasesContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig(location1 = "location1")
static class Location1ImplicitAliasesContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig(location2 = "location2")
static class Location2ImplicitAliasesContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesContextConfig(location3 = "location3")
static class Location3ImplicitAliasesContextConfigClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig {
// intentionally omitted: attribute = "value"
@AliasFor(annotation = ContextConfig.class)
String value() default "";
// intentionally omitted: attribute = "locations"
@AliasFor(annotation = ContextConfig.class)
String location() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlFile() default "";
}
@ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesWithImpliedAliasNamesOmittedContextConfig {
@AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "xmlFile")
String xml() default "";
@AliasFor(annotation = ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig.class, attribute = "location")
String groovy() default "";
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig("value")
static class ValueImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(location = "location")
static class LocationsImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
}
// Attribute value intentionally matches attribute name:
@ImplicitAliasesWithImpliedAliasNamesOmittedContextConfig(xmlFile = "xmlFile")
static class XmlFilesImplicitAliasesWithImpliedAliasNamesOmittedContextConfigClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesWithMissingDefaultValuesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location1();
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location2();
}
@ImplicitAliasesWithMissingDefaultValuesContextConfig(location1 = "1", location2 = "2")
static class ImplicitAliasesWithMissingDefaultValuesContextConfigClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesWithDifferentDefaultValuesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location1() default "foo";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location2() default "bar";
}
@ImplicitAliasesWithDifferentDefaultValuesContextConfig(location1 = "1", location2 = "2")
static class ImplicitAliasesWithDifferentDefaultValuesContextConfigClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesWithDuplicateValuesContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location1() default "";
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String location2() default "";
}
@ImplicitAliasesWithDuplicateValuesContextConfig(location1 = "1", location2 = "2")
static class ImplicitAliasesWithDuplicateValuesContextConfigClass {
}
@ContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface ImplicitAliasesForAliasPairContextConfig {
@AliasFor(annotation = ContextConfig.class, attribute = "location")
String xmlFile() default "";
@AliasFor(annotation = ContextConfig.class, value = "value")
String groovyScript() default "";
}
@ImplicitAliasesForAliasPairContextConfig(xmlFile = "test.xml")
static class ImplicitAliasesForAliasPairContextConfigClass {
}
@ImplicitAliasesContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesContextConfig {
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "xmlFile")
String xml() default "";
@AliasFor(annotation = ImplicitAliasesContextConfig.class, attribute = "groovyScript")
String groovy() default "";
}
@TransitiveImplicitAliasesContextConfig(xml = "test.xml")
static class TransitiveImplicitAliasesContextConfigClass {
}
@ImplicitAliasesForAliasPairContextConfig
@Retention(RetentionPolicy.RUNTIME)
@interface TransitiveImplicitAliasesForAliasPairContextConfig {
@AliasFor(annotation = ImplicitAliasesForAliasPairContextConfig.class, attribute = "xmlFile")
String xml() default "";
@AliasFor(annotation = ImplicitAliasesForAliasPairContextConfig.class, attribute = "groovyScript")
String groovy() default "";
}
@TransitiveImplicitAliasesForAliasPairContextConfig(xml = "test.xml")
static class TransitiveImplicitAliasesForAliasPairContextConfigClass {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
String pattern();
}
/**
* Mock of {@code org.springframework.context.annotation.ComponentScan}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScan {
Filter[] excludeFilters() default {};
}
@ComponentScan(excludeFilters = {@Filter(pattern = "*Foo"), @Filter(pattern = "*Bar")})
static class ComponentScanClass {
}
/**
* Mock of {@code org.springframework.context.annotation.ComponentScan}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface ComponentScanSingleFilter {
Filter value();
}
@ComponentScanSingleFilter(@Filter(pattern = "*Foo"))
static class ComponentScanSingleFilterClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationWithDefaults {
String text() default "enigma";
boolean predicate() default true;
char[] characters() default {'a', 'b', 'c'};
}
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationWithoutDefaults {
String text();
}
@ContextConfig(value = "foo", location = "bar")
interface ContextConfigMismatch {
}
}