Consistent alias processing behind AnnotatedTypeMetadata abstraction (also for ASM)

Issue: SPR-14427
This commit is contained in:
Juergen Hoeller
2016-07-15 13:59:25 +02:00
parent b1663585dc
commit 3d3407c789
21 changed files with 320 additions and 679 deletions

View File

@@ -18,16 +18,11 @@ 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.*;
@@ -133,21 +128,21 @@ public class AnnotationAttributesTests {
@Test
public void getEnumWithNullAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("attributeName must not be null or empty"));
exception.expectMessage("must not be null or empty");
attributes.getEnum(null);
}
@Test
public void getEnumWithEmptyAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("attributeName must not be null or empty"));
exception.expectMessage("must not be null or empty");
attributes.getEnum("");
}
@Test
public void getEnumWithUnknownAttributeName() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("Attribute 'bogus' not found"));
exception.expectMessage("Attribute 'bogus' not found");
attributes.getEnum("bogus");
}
@@ -159,337 +154,6 @@ public class AnnotationAttributesTests {
attributes.getEnum("color");
}
@Test
public void getAliasedString() {
final String value = "metaverse";
attributes.clear();
attributes.put("name", value);
assertEquals(value, getAliasedString("name"));
assertEquals(value, getAliasedString("value"));
attributes.clear();
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
public void getAliasedStringFromSynthesizedAnnotationAttributes() {
Scope scope = ScopedComponent.class.getAnnotation(Scope.class);
AnnotationAttributes scopeAttributes = AnnotationUtils.getAnnotationAttributes(ScopedComponent.class, scope);
assertEquals("custom", getAliasedString(scopeAttributes, "name"));
assertEquals("custom", getAliasedString(scopeAttributes, "value"));
}
@Test
public void getAliasedStringWithMissingAliasedAttributes() {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(equalTo("Neither attribute 'name' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedString("name");
}
@Test
public void getAliasedStringWithDifferentAliasedValues() {
attributes.put("name", "request");
attributes.put("value", "session");
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("In annotation [" + Scope.class.getName() + "]"));
exception.expectMessage(containsString("attribute [name] and its alias [value]"));
exception.expectMessage(containsString("[request] and [session]"));
exception.expectMessage(containsString("but only one is permitted"));
getAliasedString("name");
}
private String getAliasedString(String attributeName) {
return getAliasedString(this.attributes, attributeName);
}
private String getAliasedString(AnnotationAttributes attrs, String attributeName) {
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("location", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", INPUT);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", INPUT);
attributes.put("value", EMPTY);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", EMPTY);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedStringArray("location"));
assertArrayEquals(INPUT, getAliasedStringArray("value"));
attributes.clear();
attributes.put("location", EMPTY);
attributes.put("value", EMPTY);
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 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedStringArray("location");
}
@Test
public void getAliasedStringArrayWithDifferentAliasedValues() {
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 [location] and its alias [value]"));
exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]"));
exception.expectMessage(containsString("but only one is permitted"));
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};
final Class<?>[] EMPTY = new Class<?>[0];
attributes.clear();
attributes.put("classes", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", INPUT);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", INPUT);
attributes.put("value", EMPTY);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", EMPTY);
attributes.put("value", INPUT);
assertArrayEquals(INPUT, getAliasedClassArray("classes"));
assertArrayEquals(INPUT, getAliasedClassArray("value"));
attributes.clear();
attributes.put("classes", EMPTY);
attributes.put("value", EMPTY);
assertArrayEquals(EMPTY, getAliasedClassArray("classes"));
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 one of its aliases [value] was found in attributes for annotation [unknown]"));
getAliasedClassArray("classes");
}
@Test
public void getAliasedClassArrayWithDifferentAliasedValues() {
attributes.put("classes", new Class<?>[] {String.class});
attributes.put("value", new Class<?>[] {Number.class});
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(containsString("In annotation [" + Filter.class.getName() + "]"));
exception.expectMessage(containsString("attribute [classes] and its alias [value]"));
exception.expectMessage(containsString("[{class java.lang.String}] and [{class java.lang.Number}]"));
exception.expectMessage(containsString("but only one is permitted"));
getAliasedClassArray("classes");
}
private Class<?>[] getAliasedClassArray(String attributeName) {
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 {
@@ -514,23 +178,4 @@ public class AnnotationAttributesTests {
static class FilteredClass {
}
/**
* Mock of {@code org.springframework.context.annotation.Scope}.
*/
@Retention(RetentionPolicy.RUNTIME)
@interface Scope {
@AliasFor(attribute = "name")
String value() default "singleton";
@AliasFor(attribute = "value")
String name() default "singleton";
}
@Scope(name = "custom")
static class ScopedComponent {
}
}

View File

@@ -30,6 +30,7 @@ import java.util.Set;
import org.junit.Test;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
@@ -291,6 +292,7 @@ public class AnnotationMetadataTests {
Set<MethodMetadata> methods = metadata.getAnnotatedMethods(DirectAnnotation.class.getName());
MethodMetadata method = methods.iterator().next();
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("value"));
assertEquals("direct", method.getAnnotationAttributes(DirectAnnotation.class.getName()).get("myValue"));
List<Object> allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("value");
assertThat(new HashSet<>(allMeta), is(equalTo(new HashSet<Object>(Arrays.asList("direct", "meta")))));
allMeta = method.getAllAnnotationAttributes(DirectAnnotation.class.getName()).get("additional");
@@ -416,7 +418,11 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
public @interface DirectAnnotation {
String value();
@AliasFor("myValue")
String value() default "";
@AliasFor("value")
String myValue() default "";
String additional() default "direct";
}
@@ -449,7 +455,7 @@ public class AnnotationMetadataTests {
}
// SPR-10914
public static enum SubclassEnum {
public enum SubclassEnum {
FOO {
/* Do not delete! This subclassing is intentional. */
},
@@ -489,14 +495,14 @@ public class AnnotationMetadataTests {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public static @interface TestConfiguration {
public @interface TestConfiguration {
String value() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface TestComponentScan {
public @interface TestComponentScan {
String[] value() default {};
@@ -509,7 +515,7 @@ public class AnnotationMetadataTests {
@TestComponentScan(basePackages = "bogus")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface ComposedConfigurationWithAttributeOverrides {
public @interface ComposedConfigurationWithAttributeOverrides {
String[] basePackages() default {};
}
@@ -520,19 +526,19 @@ public class AnnotationMetadataTests {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation1 {
public @interface NamedAnnotation1 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation2 {
public @interface NamedAnnotation2 {
String name() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedAnnotation3 {
public @interface NamedAnnotation3 {
String name() default "";
}
@@ -547,7 +553,7 @@ public class AnnotationMetadataTests {
@NamedAnnotation3(name = "name 3")
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public static @interface NamedComposedAnnotation {
public @interface NamedComposedAnnotation {
}
@NamedComposedAnnotation