Support implicit attribute aliases with @AliasFor
Spring Framework 4.2 introduced support for aliases between annotation attributes that fall into the following two categories. 1) Alias pairs: two attributes in the same annotation that use @AliasFor to declare that they are explicit aliases for each other. 2) Meta-annotation attribute overrides: an attribute in one annotation uses @AliasFor to declare that it is an explicit override of an attribute in a meta-annotation. However, the existing functionality fails to support the case where two attributes in the same annotation both use @AliasFor to declare that they are both explicit overrides of the same attribute in the same meta-annotation. In such scenarios, one would intuitively assume that two such attributes would be treated as "implicit" aliases for each other, analogous to the existing support for explicit alias pairs. Furthermore, an annotation may potentially declare multiple aliases that are effectively a set of implicit aliases for each other. This commit introduces support for implicit aliases configured via @AliasFor through an extensive overhaul of the support for alias lookups, validation, etc. Specifically, this commit includes the following. - Introduced isAnnotationMetaPresent() in AnnotationUtils. - Introduced private AliasDescriptor class in AnnotationUtils in order to encapsulate the parsing, validation, and comparison of both explicit and implicit aliases configured via @AliasFor. - Switched from single values for alias names to lists of alias names. - Renamed getAliasedAttributeName() to getAliasedAttributeNames() in AnnotationUtils. - Converted alias map to contain lists of aliases in AnnotationUtils. - Refactored the following to support multiple implicit aliases: getRequiredAttributeWithAlias() in AnnotationAttributes, AbstractAliasAwareAnnotationAttributeExtractor, MapAnnotationAttributeExtractor, MergedAnnotationAttributesProcessor in AnnotatedElementUtils, and postProcessAnnotationAttributes() in AnnotationUtils. - Introduced numerous tests for implicit alias support, including AbstractAliasAwareAnnotationAttributeExtractorTestCase, DefaultAnnotationAttributeExtractorTests, and MapAnnotationAttributeExtractorTests. - Updated Javadoc in @AliasFor regarding implicit aliases and in AnnotationUtils regarding "meta-present". Issue: SPR-13345
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.GroovyImplicitAliasesContextConfigClass;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location1ImplicitAliasesContextConfigClass;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location2ImplicitAliasesContextConfigClass;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.Location3ImplicitAliasesContextConfigClass;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ValueImplicitAliasesContextConfigClass;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.XmlImplicitAliasesContextConfigClass;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Abstract base class for tests involving concrete implementations of
|
||||
* {@link AbstractAliasAwareAnnotationAttributeExtractor}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public abstract class AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
||||
|
||||
@Test
|
||||
public void getAttributeValueForImplicitAliases() throws Exception {
|
||||
assertGetAttributeValueForImplicitAliases(GroovyImplicitAliasesContextConfigClass.class, "groovyScript");
|
||||
assertGetAttributeValueForImplicitAliases(XmlImplicitAliasesContextConfigClass.class, "xmlFile");
|
||||
assertGetAttributeValueForImplicitAliases(ValueImplicitAliasesContextConfigClass.class, "value");
|
||||
assertGetAttributeValueForImplicitAliases(Location1ImplicitAliasesContextConfigClass.class, "location1");
|
||||
assertGetAttributeValueForImplicitAliases(Location2ImplicitAliasesContextConfigClass.class, "location2");
|
||||
assertGetAttributeValueForImplicitAliases(Location3ImplicitAliasesContextConfigClass.class, "location3");
|
||||
}
|
||||
|
||||
private void assertGetAttributeValueForImplicitAliases(Class<?> clazz, String expected) throws Exception {
|
||||
Method xmlFile = ImplicitAliasesContextConfig.class.getDeclaredMethod("xmlFile");
|
||||
Method groovyScript = ImplicitAliasesContextConfig.class.getDeclaredMethod("groovyScript");
|
||||
Method value = ImplicitAliasesContextConfig.class.getDeclaredMethod("value");
|
||||
|
||||
AnnotationAttributeExtractor<?> extractor = createExtractorFor(clazz, expected, ImplicitAliasesContextConfig.class);
|
||||
|
||||
assertThat(extractor.getAttributeValue(value), is(expected));
|
||||
assertThat(extractor.getAttributeValue(groovyScript), is(expected));
|
||||
assertThat(extractor.getAttributeValue(xmlFile), is(expected));
|
||||
}
|
||||
|
||||
protected abstract AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType);
|
||||
|
||||
}
|
||||
@@ -323,16 +323,62 @@ public class AnnotatedElementUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMergeAndSynthesizeAnnotationWithAliasedValueComposedAnnotation() {
|
||||
Class<?> element = AliasedValueComposedContextConfigClass.class;
|
||||
public void getMergedAnnotationAttributesWithImplicitAliasesInMetaAnnotationOnComposedAnnotation() {
|
||||
Class<?> element = ComposedImplicitAliasesContextConfigClass.class;
|
||||
String name = ImplicitAliasesContextConfig.class.getName();
|
||||
AnnotationAttributes attributes = getMergedAnnotationAttributes(element, name);
|
||||
String[] expected = new String[] { "A.xml", "B.xml" };
|
||||
|
||||
assertNotNull("Should find @ImplicitAliasesContextConfig on " + element.getSimpleName(), attributes);
|
||||
assertArrayEquals("groovyScripts", expected, attributes.getStringArray("groovyScripts"));
|
||||
assertArrayEquals("xmlFiles", expected, attributes.getStringArray("xmlFiles"));
|
||||
assertArrayEquals("locations", expected, attributes.getStringArray("locations"));
|
||||
assertArrayEquals("value", expected, attributes.getStringArray("value"));
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMergedAnnotationWithAliasedValueComposedAnnotation() {
|
||||
assertGetMergedAnnotation(AliasedValueComposedContextConfigClass.class, "test.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMergedAnnotationWithImplicitAliasesForSameAttributeInComposedAnnotation() {
|
||||
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass1.class, "foo.xml");
|
||||
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass2.class, "bar.xml");
|
||||
assertGetMergedAnnotation(ImplicitAliasesContextConfigClass3.class, "baz.xml");
|
||||
}
|
||||
|
||||
private void assertGetMergedAnnotation(Class<?> element, String expected) {
|
||||
String name = ContextConfig.class.getName();
|
||||
ContextConfig contextConfig = getMergedAnnotation(element, ContextConfig.class);
|
||||
|
||||
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), contextConfig);
|
||||
assertArrayEquals("locations", new String[] { "test.xml" }, contextConfig.locations());
|
||||
assertArrayEquals("value", new String[] { "test.xml" }, contextConfig.value());
|
||||
assertArrayEquals("locations", new String[] { expected }, contextConfig.locations());
|
||||
assertArrayEquals("value", new String[] { expected }, contextConfig.value());
|
||||
assertArrayEquals("classes", new Class<?>[0], contextConfig.classes());
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, ContextConfig.class.getName()));
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getMergedAnnotationWithImplicitAliasesInMetaAnnotationOnComposedAnnotation() {
|
||||
Class<?> element = ComposedImplicitAliasesContextConfigClass.class;
|
||||
String name = ImplicitAliasesContextConfig.class.getName();
|
||||
ImplicitAliasesContextConfig config = getMergedAnnotation(element, ImplicitAliasesContextConfig.class);
|
||||
String[] expected = new String[] { "A.xml", "B.xml" };
|
||||
|
||||
assertNotNull("Should find @ImplicitAliasesContextConfig on " + element.getSimpleName(), config);
|
||||
assertArrayEquals("groovyScripts", expected, config.groovyScripts());
|
||||
assertArrayEquals("xmlFiles", expected, config.xmlFiles());
|
||||
assertArrayEquals("locations", expected, config.locations());
|
||||
assertArrayEquals("value", expected, config.value());
|
||||
|
||||
// Verify contracts between utility methods:
|
||||
assertTrue(isAnnotated(element, name));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -517,11 +563,11 @@ public class AnnotatedElementUtilsTests {
|
||||
|
||||
@Test
|
||||
public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
|
||||
String[] expected = new String[] { "com.example.app.test" };
|
||||
Class<?> element = TestComponentScanClass.class;
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
|
||||
assertNotNull("Should find @ComponentScan on " + element, attributes);
|
||||
assertArrayEquals("basePackages for " + element, new String[] { "com.example.app.test" },
|
||||
attributes.getStringArray("basePackages"));
|
||||
assertArrayEquals("basePackages for " + element, expected, attributes.getStringArray("basePackages"));
|
||||
|
||||
Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
|
||||
assertNotNull(excludeFilters);
|
||||
@@ -530,6 +576,22 @@ public class AnnotatedElementUtilsTests {
|
||||
assertEquals(asList("*Test", "*Tests"), patterns);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test ensures that {@link AnnotationUtils#postProcessAnnotationAttributes}
|
||||
* uses {@code ObjectUtils.nullSafeEquals()} to check for equality between annotation
|
||||
* attributes since attributes may be arrays.
|
||||
*/
|
||||
@Test
|
||||
public void findMergedAnnotationAttributesOnClassWithBothAttributesOfAnAliasPairDeclared() {
|
||||
String[] expected = new String[] { "com.example.app.test" };
|
||||
Class<?> element = ComponentScanWithBasePackagesAndValueAliasClass.class;
|
||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
|
||||
|
||||
assertNotNull("Should find @ComponentScan on " + element, attributes);
|
||||
assertArrayEquals("value: ", expected, attributes.getStringArray("value"));
|
||||
assertArrayEquals("basePackages: ", expected, attributes.getStringArray("basePackages"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
|
||||
final String[] EMPTY = new String[] {};
|
||||
@@ -716,6 +778,28 @@ public class AnnotatedElementUtilsTests {
|
||||
String[] locations();
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ImplicitAliasesContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] groovyScripts() default {};
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] xmlFiles() default {};
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] locations() default {};
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "locations")
|
||||
String[] value() default {};
|
||||
}
|
||||
|
||||
@ImplicitAliasesContextConfig(xmlFiles = { "A.xml", "B.xml" })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ComposedImplicitAliasesContextConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalid because the configuration declares a value for 'value' and
|
||||
* requires a value for the aliased 'locations'. So we likely end up with
|
||||
@@ -762,6 +846,10 @@ public class AnnotatedElementUtilsTests {
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ComponentScan {
|
||||
|
||||
@AliasFor("basePackages")
|
||||
String[] value() default {};
|
||||
|
||||
@AliasFor("value")
|
||||
String[] basePackages() default {};
|
||||
|
||||
Filter[] excludeFilters() default {};
|
||||
@@ -928,6 +1016,22 @@ public class AnnotatedElementUtilsTests {
|
||||
static class AliasedValueComposedContextConfigClass {
|
||||
}
|
||||
|
||||
@ImplicitAliasesContextConfig("foo.xml")
|
||||
static class ImplicitAliasesContextConfigClass1 {
|
||||
}
|
||||
|
||||
@ImplicitAliasesContextConfig(locations = "bar.xml")
|
||||
static class ImplicitAliasesContextConfigClass2 {
|
||||
}
|
||||
|
||||
@ImplicitAliasesContextConfig(xmlFiles = "baz.xml")
|
||||
static class ImplicitAliasesContextConfigClass3 {
|
||||
}
|
||||
|
||||
@ComposedImplicitAliasesContextConfig
|
||||
static class ComposedImplicitAliasesContextConfigClass {
|
||||
}
|
||||
|
||||
@InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||
static class InvalidAliasedComposedContextConfigClass {
|
||||
}
|
||||
@@ -936,6 +1040,10 @@ public class AnnotatedElementUtilsTests {
|
||||
static class AliasedComposedContextConfigAndTestPropSourceClass {
|
||||
}
|
||||
|
||||
@ComponentScan(value = "com.example.app.test", basePackages = "com.example.app.test")
|
||||
static class ComponentScanWithBasePackagesAndValueAliasClass {
|
||||
}
|
||||
|
||||
@TestComponentScan(packages = "com.example.app.test")
|
||||
static class TestComponentScanClass {
|
||||
}
|
||||
|
||||
@@ -18,11 +18,16 @@ package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ContextConfig;
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -36,7 +41,7 @@ import static org.junit.Assert.*;
|
||||
*/
|
||||
public class AnnotationAttributesTests {
|
||||
|
||||
private final AnnotationAttributes attributes = new AnnotationAttributes();
|
||||
private AnnotationAttributes attributes = new AnnotationAttributes();
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
@@ -156,21 +161,56 @@ public class AnnotationAttributesTests {
|
||||
|
||||
@Test
|
||||
public void getAliasedString() {
|
||||
attributes.clear();
|
||||
attributes.put("name", "metaverse");
|
||||
assertEquals("metaverse", getAliasedString("name"));
|
||||
assertEquals("metaverse", getAliasedString("value"));
|
||||
final String value = "metaverse";
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", "metaverse");
|
||||
assertEquals("metaverse", getAliasedString("name"));
|
||||
assertEquals("metaverse", getAliasedString("value"));
|
||||
attributes.put("name", value);
|
||||
assertEquals(value, getAliasedString("name"));
|
||||
assertEquals(value, getAliasedString("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("name", "metaverse");
|
||||
attributes.put("value", "metaverse");
|
||||
assertEquals("metaverse", getAliasedString("name"));
|
||||
assertEquals("metaverse", getAliasedString("value"));
|
||||
attributes.put("value", value);
|
||||
assertEquals(value, getAliasedString("name"));
|
||||
assertEquals(value, getAliasedString("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("name", value);
|
||||
attributes.put("value", value);
|
||||
assertEquals(value, getAliasedString("name"));
|
||||
assertEquals(value, getAliasedString("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringWithImplicitAliases() {
|
||||
final String value = "metaverse";
|
||||
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
|
||||
|
||||
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
|
||||
attributes.put("value", value);
|
||||
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", value);
|
||||
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", value);
|
||||
attributes.put("location1", value);
|
||||
attributes.put("xmlFile", value);
|
||||
attributes.put("groovyScript", value);
|
||||
aliases.stream().forEach(alias -> assertEquals(value, getAliasedStringWithImplicitAliases(alias)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringWithImplicitAliasesWithMissingAliasedAttributes() {
|
||||
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
|
||||
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
|
||||
aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
|
||||
exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
|
||||
getAliasedStringWithImplicitAliases("value");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -185,7 +225,7 @@ public class AnnotationAttributesTests {
|
||||
@Test
|
||||
public void getAliasedStringWithMissingAliasedAttributes() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(equalTo("Neither attribute 'name' nor its alias 'value' was found in attributes for annotation [unknown]"));
|
||||
exception.expectMessage(equalTo("Neither attribute 'name' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
|
||||
getAliasedString("name");
|
||||
}
|
||||
|
||||
@@ -211,71 +251,135 @@ public class AnnotationAttributesTests {
|
||||
return attrs.getAliasedString(attributeName, Scope.class, null);
|
||||
}
|
||||
|
||||
private String getAliasedStringWithImplicitAliases(String attributeName) {
|
||||
return this.attributes.getAliasedString(attributeName, ImplicitAliasesContextConfig.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArray() {
|
||||
final String[] INPUT = new String[] { "test.xml" };
|
||||
final String[] EMPTY = new String[0];
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
attributes.put("location", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", INPUT);
|
||||
attributes.put("location", INPUT);
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", INPUT);
|
||||
attributes.put("location", INPUT);
|
||||
attributes.put("value", EMPTY);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", EMPTY);
|
||||
attributes.put("location", EMPTY);
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", EMPTY);
|
||||
attributes.put("location", EMPTY);
|
||||
attributes.put("value", EMPTY);
|
||||
assertArrayEquals(EMPTY, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(EMPTY, getAliasedStringArray("location"));
|
||||
assertArrayEquals(EMPTY, getAliasedStringArray("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArrayWithImplicitAliases() {
|
||||
final String[] INPUT = new String[] { "test.xml" };
|
||||
final String[] EMPTY = new String[0];
|
||||
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
|
||||
|
||||
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
|
||||
|
||||
attributes.put("location1", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", INPUT);
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", INPUT);
|
||||
attributes.put("value", EMPTY);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", EMPTY);
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", EMPTY);
|
||||
attributes.put("value", EMPTY);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedStringArrayWithImplicitAliases(alias)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArrayWithImplicitAliasesWithMissingAliasedAttributes() {
|
||||
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
|
||||
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(startsWith("Neither attribute 'value' nor one of its aliases ["));
|
||||
aliases.stream().forEach(alias -> exception.expectMessage(containsString(alias)));
|
||||
exception.expectMessage(endsWith("] was found in attributes for annotation [" + ImplicitAliasesContextConfig.class.getName() + "]"));
|
||||
getAliasedStringArrayWithImplicitAliases("value");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArrayWithMissingAliasedAttributes() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(equalTo("Neither attribute 'locations' nor its alias 'value' was found in attributes for annotation [unknown]"));
|
||||
getAliasedStringArray("locations");
|
||||
exception.expectMessage(equalTo("Neither attribute 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
|
||||
getAliasedStringArray("location");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArrayWithDifferentAliasedValues() {
|
||||
attributes.put("locations", new String[] { "1.xml" });
|
||||
attributes.put("location", new String[] { "1.xml" });
|
||||
attributes.put("value", new String[] { "2.xml" });
|
||||
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("In annotation [" + ContextConfig.class.getName() + "]"));
|
||||
exception.expectMessage(containsString("attribute [locations] and its alias [value]"));
|
||||
exception.expectMessage(containsString("attribute [location] and its alias [value]"));
|
||||
exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]"));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
|
||||
getAliasedStringArray("locations");
|
||||
getAliasedStringArray("location");
|
||||
}
|
||||
|
||||
private String[] getAliasedStringArray(String attributeName) {
|
||||
// Note: even though the attributes we test against here are of type
|
||||
// String instead of String[], it doesn't matter... since
|
||||
// AnnotationAttributes does not validate the actual return type of
|
||||
// attributes in the annotation.
|
||||
return attributes.getAliasedStringArray(attributeName, ContextConfig.class, null);
|
||||
}
|
||||
|
||||
private String[] getAliasedStringArrayWithImplicitAliases(String attributeName) {
|
||||
// Note: even though the attributes we test against here are of type
|
||||
// String instead of String[], it doesn't matter... since
|
||||
// AnnotationAttributes does not validate the actual return type of
|
||||
// attributes in the annotation.
|
||||
return this.attributes.getAliasedStringArray(attributeName, ImplicitAliasesContextConfig.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedClassArray() {
|
||||
final Class<?>[] INPUT = new Class<?>[] { String.class };
|
||||
@@ -316,10 +420,46 @@ public class AnnotationAttributesTests {
|
||||
assertArrayEquals(EMPTY, getAliasedClassArray("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedClassArrayWithImplicitAliases() {
|
||||
final Class<?>[] INPUT = new Class<?>[] { String.class };
|
||||
final Class<?>[] EMPTY = new Class<?>[0];
|
||||
final List<String> aliases = Arrays.asList("value", "location1", "location2", "location3", "xmlFile", "groovyScript");
|
||||
|
||||
attributes = new AnnotationAttributes(ImplicitAliasesContextConfig.class);
|
||||
|
||||
attributes.put("location1", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", INPUT);
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", INPUT);
|
||||
attributes.put("value", EMPTY);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", EMPTY);
|
||||
attributes.put("value", INPUT);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(INPUT, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("location1", EMPTY);
|
||||
attributes.put("value", EMPTY);
|
||||
aliases.stream().forEach(alias -> assertArrayEquals(EMPTY, getAliasedClassArrayWithImplicitAliases(alias)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedClassArrayWithMissingAliasedAttributes() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(equalTo("Neither attribute 'classes' nor its alias 'value' was found in attributes for annotation [unknown]"));
|
||||
exception.expectMessage(equalTo("Neither attribute 'classes' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
|
||||
getAliasedClassArray("classes");
|
||||
}
|
||||
|
||||
@@ -341,6 +481,14 @@ public class AnnotationAttributesTests {
|
||||
return attributes.getAliasedClassArray(attributeName, Filter.class, null);
|
||||
}
|
||||
|
||||
private Class<?>[] getAliasedClassArrayWithImplicitAliases(String attributeName) {
|
||||
// Note: even though the attributes we test against here are of type
|
||||
// String instead of Class<?>[], it doesn't matter... since
|
||||
// AnnotationAttributes does not validate the actual return type of
|
||||
// attributes in the annotation.
|
||||
return this.attributes.getAliasedClassArray(attributeName, ImplicitAliasesContextConfig.class, null);
|
||||
}
|
||||
|
||||
|
||||
enum Color {
|
||||
RED, WHITE, BLUE
|
||||
@@ -362,19 +510,6 @@ public class AnnotationAttributesTests {
|
||||
static class FilteredClass {
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@code org.springframework.test.context.ContextConfiguration}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ContextConfig {
|
||||
|
||||
@AliasFor(attribute = "locations")
|
||||
String value() default "";
|
||||
|
||||
@AliasFor(attribute = "value")
|
||||
String locations() default "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock of {@code org.springframework.context.annotation.Scope}.
|
||||
*/
|
||||
|
||||
@@ -22,14 +22,14 @@ 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.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
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;
|
||||
@@ -38,6 +38,7 @@ 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.*;
|
||||
@@ -56,10 +57,31 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
*/
|
||||
public class AnnotationUtilsTests {
|
||||
|
||||
static void clearCaches() {
|
||||
clearCache("findAnnotationCache", "annotatedInterfaceCache", "metaPresentCache", "synthesizableCache",
|
||||
"attributeAliasesCache", "attributeMethodsCache");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
|
||||
@Before
|
||||
public void clearCachesBeforeTests() {
|
||||
clearCaches();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMethodAnnotationOnLeaf() throws Exception {
|
||||
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
||||
@@ -308,7 +330,7 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
|
||||
// no class-level annotation
|
||||
List<Class<? extends Annotation>> transactionalCandidateList = Arrays.<Class<? extends Annotation>> asList(Transactional.class);
|
||||
List<Class<? extends Annotation>> transactionalCandidateList = asList(Transactional.class);
|
||||
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
|
||||
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
|
||||
|
||||
@@ -323,7 +345,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
// non-inherited class-level annotation; note: @Order is not inherited,
|
||||
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
|
||||
List<Class<? extends Annotation>> orderCandidateList = Arrays.<Class<? extends Annotation>> asList(Order.class);
|
||||
List<Class<? extends Annotation>> orderCandidateList = asList(Order.class);
|
||||
assertEquals(NonInheritedAnnotationInterface.class,
|
||||
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
|
||||
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
|
||||
@@ -335,7 +357,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void findAnnotationDeclaringClassForTypesWithMultipleCandidateTypes() {
|
||||
List<Class<? extends Annotation>> candidates = Arrays.<Class<? extends Annotation>> asList(Transactional.class, Order.class);
|
||||
List<Class<? extends Annotation>> candidates = asList(Transactional.class, Order.class);
|
||||
|
||||
// no class-level annotation
|
||||
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
|
||||
@@ -461,7 +483,7 @@ public class AnnotationUtilsTests {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
|
||||
exception.expectMessage(containsString("values of [/enigma] and [/test]"));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
exception.expectMessage(endsWith("but only one is permitted."));
|
||||
getAnnotationAttributes(webMapping);
|
||||
}
|
||||
|
||||
@@ -524,21 +546,21 @@ public class AnnotationUtilsTests {
|
||||
Set<MyRepeatable> annotations = getRepeatableAnnotations(method, MyRepeatable.class, MyRepeatableContainer.class);
|
||||
assertNotNull(annotations);
|
||||
List<String> values = annotations.stream().map(MyRepeatable::value).collect(toList());
|
||||
assertThat(values, is(Arrays.asList("A", "B", "C", "meta1")));
|
||||
assertThat(values, is(asList("A", "B", "C", "meta1")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [value] in"));
|
||||
exception.expectMessage(startsWith("Attribute [value] in"));
|
||||
exception.expectMessage(containsString(BrokenContextConfig.class.getName()));
|
||||
exception.expectMessage(containsString("must be declared as an @AliasFor [location]"));
|
||||
exception.expectMessage(endsWith("must be declared as an @AliasFor [location]."));
|
||||
getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception {
|
||||
final List<String> expectedLocations = Arrays.asList("A", "B");
|
||||
final List<String> expectedLocations = asList("A", "B");
|
||||
|
||||
Set<ContextConfig> annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, null);
|
||||
assertNotNull(annotations);
|
||||
@@ -556,8 +578,8 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnClass() {
|
||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
||||
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);
|
||||
@@ -581,8 +603,8 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnSuperclass() {
|
||||
final Class<?> clazz = SubMyRepeatableClass.class;
|
||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
||||
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);
|
||||
@@ -606,8 +628,8 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnClassAndSuperclass() {
|
||||
final Class<?> clazz = SubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||
final List<String> expectedValuesJava = Arrays.asList("X", "Y", "Z");
|
||||
final List<String> expectedValuesSpring = Arrays.asList("X", "Y", "Z", "meta2");
|
||||
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);
|
||||
@@ -631,8 +653,8 @@ public class AnnotationUtilsTests {
|
||||
@Test
|
||||
public void getRepeatableAnnotationsDeclaredOnMultipleSuperclasses() {
|
||||
final Class<?> clazz = SubSubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||
final List<String> expectedValuesJava = Arrays.asList("X", "Y", "Z");
|
||||
final List<String> expectedValuesSpring = Arrays.asList("X", "Y", "Z", "meta2");
|
||||
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);
|
||||
@@ -655,8 +677,8 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void getDeclaredRepeatableAnnotationsDeclaredOnClass() {
|
||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
||||
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);
|
||||
@@ -698,16 +720,45 @@ public class AnnotationUtilsTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedAttributeNameFromWrongTargetAnnotation() throws Exception {
|
||||
public void getAliasedAttributeNamesFromWrongTargetAnnotation() throws Exception {
|
||||
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||
assertNull("xmlConfigFile is not an alias for @Component.",
|
||||
getAliasedAttributeName(attribute, Component.class));
|
||||
assertThat("xmlConfigFile is not an alias for @Component.",
|
||||
getAliasedAttributeNames(attribute, Component.class), is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedAttributeNameFromAliasedComposedAnnotation() throws Exception {
|
||||
public void getAliasedAttributeNamesForNonAliasedAttribute() throws Exception {
|
||||
Method nonAliasedAttribute = ImplicitAliasesContextConfig.class.getDeclaredMethod("nonAliasedAttribute");
|
||||
assertThat(getAliasedAttributeNames(nonAliasedAttribute, ContextConfig.class), is(empty()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedAttributeNamesFromAliasedComposedAnnotation() throws Exception {
|
||||
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||
assertEquals("location", getAliasedAttributeName(attribute, ContextConfig.class));
|
||||
assertEquals(asList("location"), getAliasedAttributeNames(attribute, ContextConfig.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedAttributeNamesFromComposedAnnotationWithImplicitAliases() 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(asList("location"), getAliasedAttributeNames(xmlFile, ContextConfig.class));
|
||||
assertEquals(asList("location"), getAliasedAttributeNames(groovyScript, ContextConfig.class));
|
||||
assertEquals(asList("location"), getAliasedAttributeNames(value, ContextConfig.class));
|
||||
|
||||
// Implicit Aliases
|
||||
assertThat(getAliasedAttributeNames(xmlFile), containsInAnyOrder("value", "groovyScript", "location1", "location2", "location3"));
|
||||
assertThat(getAliasedAttributeNames(groovyScript), containsInAnyOrder("value", "xmlFile", "location1", "location2", "location3"));
|
||||
assertThat(getAliasedAttributeNames(value), containsInAnyOrder("xmlFile", "groovyScript", "location1", "location2", "location3"));
|
||||
assertThat(getAliasedAttributeNames(location1), containsInAnyOrder("xmlFile", "groovyScript", "value", "location2", "location3"));
|
||||
assertThat(getAliasedAttributeNames(location2), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location3"));
|
||||
assertThat(getAliasedAttributeNames(location3), containsInAnyOrder("xmlFile", "groovyScript", "value", "location1", "location2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -746,9 +797,9 @@ public class AnnotationUtilsTests {
|
||||
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
|
||||
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("@AliasFor declaration on attribute [foo] in annotation"));
|
||||
exception.expectMessage(startsWith("@AliasFor declaration on attribute [foo] in annotation"));
|
||||
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
|
||||
exception.expectMessage(containsString("is missing required 'attribute' value"));
|
||||
exception.expectMessage(endsWith("is missing required 'attribute' value."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -756,10 +807,10 @@ public class AnnotationUtilsTests {
|
||||
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
|
||||
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("In @AliasFor declared on attribute [foo] in annotation"));
|
||||
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]"));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
exception.expectMessage(endsWith("but only one is permitted."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -767,7 +818,7 @@ public class AnnotationUtilsTests {
|
||||
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
||||
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [foo] in"));
|
||||
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);
|
||||
@@ -778,9 +829,9 @@ public class AnnotationUtilsTests {
|
||||
AliasForWithoutMirroredAliasFor annotation =
|
||||
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||
exception.expectMessage(startsWith("Attribute [bar] in"));
|
||||
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
|
||||
exception.expectMessage(containsString("must be declared as an @AliasFor [foo]"));
|
||||
exception.expectMessage(endsWith("must be declared as an @AliasFor [foo]."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -789,12 +840,8 @@ public class AnnotationUtilsTests {
|
||||
AliasForWithMirroredAliasForWrongAttribute annotation =
|
||||
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
|
||||
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
// In other words, we can't be certain which type of exception message we'll get,
|
||||
// so we allow for both possibilities.
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("Attribute [bar] in"));
|
||||
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]")));
|
||||
@@ -808,13 +855,9 @@ public class AnnotationUtilsTests {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
|
||||
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare the same return type"));
|
||||
exception.expectMessage(endsWith("must declare the same return type."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -825,13 +868,9 @@ public class AnnotationUtilsTests {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
|
||||
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare default values"));
|
||||
exception.expectMessage(containsString("attribute [foo] in annotation"));
|
||||
exception.expectMessage(containsString("attribute [bar] in annotation"));
|
||||
exception.expectMessage(endsWith("must declare default values."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -842,13 +881,9 @@ public class AnnotationUtilsTests {
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
|
||||
|
||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
||||
// reflection, we cannot make the test dependent on any specific ordering.
|
||||
// In other words, we don't know if "foo" or "bar" will come first.
|
||||
exception.expectMessage(containsString("attribute [foo]"));
|
||||
exception.expectMessage(containsString("attribute [bar]"));
|
||||
exception.expectMessage(containsString("must declare the same default value"));
|
||||
exception.expectMessage(containsString("attribute [foo] in annotation"));
|
||||
exception.expectMessage(containsString("attribute [bar] in annotation"));
|
||||
exception.expectMessage(endsWith("must declare the same default value."));
|
||||
synthesizeAnnotation(annotation);
|
||||
}
|
||||
|
||||
@@ -887,13 +922,91 @@ public class AnnotationUtilsTests {
|
||||
assertEquals("actual value attribute: ", "/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));
|
||||
assertNotSame(config, synthesizedConfig);
|
||||
|
||||
assertEquals("value: ", expected, synthesizedConfig.value());
|
||||
assertEquals("location1: ", expected, synthesizedConfig.location1());
|
||||
assertEquals("xmlFile: ", expected, synthesizedConfig.xmlFile());
|
||||
assertEquals("groovyScript: ", expected, synthesizedConfig.groovyScript());
|
||||
}
|
||||
|
||||
@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(endsWith("must declare 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(endsWith("must declare the 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]")));
|
||||
exception.expectMessage(endsWith("but only one is permitted."));
|
||||
|
||||
synthesizedConfig.location1();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
|
||||
Component component = WebController.class.getAnnotation(Component.class);
|
||||
assertNotNull(component);
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put(VALUE, "webController");
|
||||
Map<String, Object> map = Collections.singletonMap(VALUE, "webController");
|
||||
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
|
||||
assertNotNull(synthesizedComponent);
|
||||
|
||||
@@ -979,14 +1092,35 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithMinimalAttributesWithAttributeAliases() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("location", "test.xml");
|
||||
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 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());
|
||||
@@ -994,8 +1128,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("text", null);
|
||||
Map<String, Object> map = Collections.singletonMap("text", null);
|
||||
assertTrue(map.containsKey("text"));
|
||||
assertMissingTextAttribute(map);
|
||||
}
|
||||
@@ -1010,8 +1143,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put(VALUE, 42L);
|
||||
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
|
||||
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage(startsWith("Attributes map"));
|
||||
@@ -1183,7 +1315,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
|
||||
List<String> expectedLocations = Arrays.asList("A", "B");
|
||||
List<String> expectedLocations = asList("A", "B");
|
||||
|
||||
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||
assertNotNull(hierarchy);
|
||||
@@ -1194,18 +1326,18 @@ public class AnnotationUtilsTests {
|
||||
ContextConfig[] configs = synthesizedHierarchy.value();
|
||||
assertNotNull(configs);
|
||||
assertTrue("nested annotations must be synthesized",
|
||||
Arrays.stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
|
||||
stream(configs).allMatch(c -> c instanceof SynthesizedAnnotation));
|
||||
|
||||
List<String> locations = Arrays.stream(configs).map(ContextConfig::location).collect(toList());
|
||||
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
|
||||
assertThat(locations, is(expectedLocations));
|
||||
|
||||
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(toList());
|
||||
List<String> values = stream(configs).map(ContextConfig::value).collect(toList());
|
||||
assertThat(values, is(expectedLocations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
|
||||
List<String> expectedLocations = Arrays.asList("A", "B");
|
||||
List<String> expectedLocations = asList("A", "B");
|
||||
|
||||
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||
assertNotNull(hierarchy);
|
||||
@@ -1216,7 +1348,7 @@ public class AnnotationUtilsTests {
|
||||
assertNotNull(contextConfig);
|
||||
|
||||
ContextConfig[] configs = synthesizedHierarchy.value();
|
||||
List<String> locations = Arrays.stream(configs).map(ContextConfig::location).collect(toList());
|
||||
List<String> locations = stream(configs).map(ContextConfig::location).collect(toList());
|
||||
assertThat(locations, is(expectedLocations));
|
||||
|
||||
// Alter array returned from synthesized annotation
|
||||
@@ -1224,7 +1356,7 @@ public class AnnotationUtilsTests {
|
||||
|
||||
// Re-retrieve the array from the synthesized annotation
|
||||
configs = synthesizedHierarchy.value();
|
||||
List<String> values = Arrays.stream(configs).map(ContextConfig::value).collect(toList());
|
||||
List<String> values = stream(configs).map(ContextConfig::value).collect(toList());
|
||||
assertThat(values, is(expectedLocations));
|
||||
}
|
||||
|
||||
@@ -1595,6 +1727,8 @@ public class AnnotationUtilsTests {
|
||||
|
||||
@AliasFor("value")
|
||||
String location() default "";
|
||||
|
||||
Class<?> klass() default Object.class;
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -1770,6 +1904,109 @@ public class AnnotationUtilsTests {
|
||||
String xmlConfigFile();
|
||||
}
|
||||
|
||||
@ContextConfig
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ImplicitAliasesContextConfig {
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, attribute = "location")
|
||||
String xmlFile() default "";
|
||||
|
||||
@AliasFor(annotation = ContextConfig.class, value = "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 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 {
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({})
|
||||
@interface Filter {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultAnnotationAttributeExtractor}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class DefaultAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
||||
|
||||
@Override
|
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
||||
return new DefaultAnnotationAttributeExtractor(clazz.getAnnotation(annotationType), clazz);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2002-2015 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.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtilsTests.ImplicitAliasesContextConfig;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MapAnnotationAttributeExtractor}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public class MapAnnotationAttributeExtractorTests extends AbstractAliasAwareAnnotationAttributeExtractorTestCase {
|
||||
|
||||
@Before
|
||||
public void clearCachesBeforeTests() {
|
||||
AnnotationUtilsTests.clearCaches();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("serial")
|
||||
public void enrichAndValidateAttributesWithImplicitAliasesAndMinimalAttributes() {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>();
|
||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
|
||||
put("groovyScript", "");
|
||||
put("xmlFile", "");
|
||||
put("value", "");
|
||||
put("location1", "");
|
||||
put("location2", "");
|
||||
put("location3", "");
|
||||
put("nonAliasedAttribute", "");
|
||||
put("configClass", Object.class);
|
||||
}};
|
||||
|
||||
assertEnrichAndValidateAttributes(attributes, expectedAttributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("serial")
|
||||
public void enrichAndValidateAttributesWithImplicitAliases() {
|
||||
Map<String, Object> attributes = new HashMap<String, Object>() {{
|
||||
put("groovyScript", "groovy!");
|
||||
}};
|
||||
|
||||
Map<String, Object> expectedAttributes = new HashMap<String, Object>() {{
|
||||
put("groovyScript", "groovy!");
|
||||
put("xmlFile", "groovy!");
|
||||
put("value", "groovy!");
|
||||
put("location1", "groovy!");
|
||||
put("location2", "groovy!");
|
||||
put("location3", "groovy!");
|
||||
put("nonAliasedAttribute", "");
|
||||
put("configClass", Object.class);
|
||||
}};
|
||||
|
||||
assertEnrichAndValidateAttributes(attributes, expectedAttributes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void assertEnrichAndValidateAttributes(Map<String, Object> sourceAttributes, Map<String, Object> expected) {
|
||||
Class<? extends Annotation> annotationType = ImplicitAliasesContextConfig.class;
|
||||
|
||||
// Since the ordering of attribute methods returned by the JVM is
|
||||
// non-deterministic, we have to rig the attributeAliasesCache in AnnotationUtils
|
||||
// so that the tests consistently fail in case enrichAndValidateAttributes() is
|
||||
// buggy.
|
||||
//
|
||||
// Otherwise, these tests would intermittently pass even for an invalid
|
||||
// implementation.
|
||||
Map<Class<? extends Annotation>, MultiValueMap<String, String>> attributeAliasesCache =
|
||||
(Map<Class<? extends Annotation>, MultiValueMap<String, String>>) AnnotationUtilsTests.getCache("attributeAliasesCache");
|
||||
|
||||
// Declare aliases in an order that will cause enrichAndValidateAttributes() to
|
||||
// fail unless it considers all aliases in the set of implicit aliases.
|
||||
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<String, String>();
|
||||
aliases.put("xmlFile", Arrays.asList("value", "groovyScript", "location1", "location2", "location3"));
|
||||
aliases.put("groovyScript", Arrays.asList("value", "xmlFile", "location1", "location2", "location3"));
|
||||
aliases.put("value", Arrays.asList("xmlFile", "groovyScript", "location1", "location2", "location3"));
|
||||
aliases.put("location1", Arrays.asList("xmlFile", "groovyScript", "value", "location2", "location3"));
|
||||
aliases.put("location2", Arrays.asList("xmlFile", "groovyScript", "value", "location1", "location3"));
|
||||
aliases.put("location3", Arrays.asList("xmlFile", "groovyScript", "value", "location1", "location2"));
|
||||
|
||||
attributeAliasesCache.put(annotationType, aliases);
|
||||
|
||||
MapAnnotationAttributeExtractor extractor = new MapAnnotationAttributeExtractor(sourceAttributes, annotationType, null);
|
||||
Map<String, Object> enriched = extractor.getSource();
|
||||
|
||||
assertEquals("attribute map size", expected.size(), enriched.size());
|
||||
expected.keySet().stream().forEach( attr ->
|
||||
assertThat("for attribute '" + attr + "'", enriched.get(attr), is(expected.get(attr))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationAttributeExtractor<?> createExtractorFor(Class<?> clazz, String expected, Class<? extends Annotation> annotationType) {
|
||||
Map<String, Object> attributes = Collections.singletonMap(expected, expected);
|
||||
return new MapAnnotationAttributeExtractor(attributes, annotationType, clazz);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user