Browse Source

Allow for a single element overriding an array attribute in a meta-annotation

Includes refinements for consistent quoting of names in exception messages.

Issue: SPR-13972
pull/976/head
Juergen Hoeller 9 years ago
parent
commit
8ff9e818a5
  1. 31
      spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
  2. 12
      spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java
  3. 6
      spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java
  4. 50
      spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

31
spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -1953,7 +1953,7 @@ public abstract class AnnotationUtils { @@ -1953,7 +1953,7 @@ public abstract class AnnotationUtils {
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
if (this.aliasedAnnotationType == this.sourceAnnotationType &&
this.aliasedAttributeName.equals(this.sourceAttributeName)) {
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] points to " +
String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
"itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
sourceAttribute.getName(), declaringClass.getName());
throw new AnnotationConfigurationException(msg);
@ -1963,7 +1963,7 @@ public abstract class AnnotationUtils { @@ -1963,7 +1963,7 @@ public abstract class AnnotationUtils {
}
catch (NoSuchMethodException ex) {
String msg = String.format(
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
"Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg, ex);
@ -1975,8 +1975,8 @@ public abstract class AnnotationUtils { @@ -1975,8 +1975,8 @@ public abstract class AnnotationUtils {
private void validate() {
// Target annotation is not meta-present?
if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " +
"an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " +
"an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@ -1985,14 +1985,14 @@ public abstract class AnnotationUtils { @@ -1985,14 +1985,14 @@ public abstract class AnnotationUtils {
if (this.isAliasPair) {
AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
throw new AnnotationConfigurationException(msg);
}
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute);
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
mirrorAliasedAttributeName);
throw new AnnotationConfigurationException(msg);
@ -2001,9 +2001,10 @@ public abstract class AnnotationUtils { @@ -2001,9 +2001,10 @@ public abstract class AnnotationUtils {
Class<?> returnType = this.sourceAttribute.getReturnType();
Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
if (returnType != aliasedReturnType) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare the same return type.",
if (returnType != aliasedReturnType &&
(!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
"and attribute '%s' in annotation [%s] must declare the same return type.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
@ -2020,16 +2021,16 @@ public abstract class AnnotationUtils { @@ -2020,16 +2021,16 @@ public abstract class AnnotationUtils {
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
if (defaultValue == null || aliasedDefaultValue == null) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare default values.",
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
"and attribute '%s' in annotation [%s] must declare default values.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare the same default value.",
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
"and attribute '%s' in annotation [%s] must declare the same default value.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
@ -2154,7 +2155,7 @@ public abstract class AnnotationUtils { @@ -2154,7 +2155,7 @@ public abstract class AnnotationUtils {
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
if (attributeDeclared && valueDeclared) {
String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " +
String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " +
"and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value);
throw new AnnotationConfigurationException(msg);

12
spring-core/src/main/java/org/springframework/core/annotation/MapAnnotationAttributeExtractor.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,7 +19,7 @@ package org.springframework.core.annotation; @@ -19,7 +19,7 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -87,7 +87,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib @@ -87,7 +87,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
private static Map<String, Object> enrichAndValidateAttributes(
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
Map<String, Object> attributes = new LinkedHashMap<String, Object>(originalAttributes);
Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
for (Method attributeMethod : getAttributeMethods(annotationType)) {
@ -121,7 +121,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib @@ -121,7 +121,7 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
// if still null
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
"Attributes map %s returned null for required attribute '%s' defined by annotation type [%s].",
attributes, attributeName, annotationType.getName()));
}
@ -155,8 +155,8 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib @@ -155,8 +155,8 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
if (!converted) {
throw new IllegalArgumentException(String.format(
"Attributes map [%s] returned a value of type [%s] for attribute [%s], "
+ "but a value of type [%s] is required as defined by annotation type [%s].",
"Attributes map %s returned a value of type [%s] for attribute '%s', " +
"but a value of type [%s] is required as defined by annotation type [%s].",
attributes, actualReturnType.getName(), attributeName, requiredReturnType.getName(),
annotationType.getName()));
}

6
spring-core/src/test/java/org/springframework/core/annotation/AnnotatedElementUtilsTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -959,7 +959,7 @@ public class AnnotatedElementUtilsTests { @@ -959,7 +959,7 @@ public class AnnotatedElementUtilsTests {
@interface TestComponentScan {
@AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
String[] packages();
String pkg();
}
// -------------------------------------------------------------------------
@ -1152,7 +1152,7 @@ public class AnnotatedElementUtilsTests { @@ -1152,7 +1152,7 @@ public class AnnotatedElementUtilsTests {
static class ComponentScanWithBasePackagesAndValueAliasClass {
}
@TestComponentScan(packages = "com.example.app.test")
@TestComponentScan(pkg = "com.example.app.test")
static class TestComponentScanClass {
}

50
spring-core/src/test/java/org/springframework/core/annotation/AnnotationUtilsTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -210,8 +210,7 @@ public class AnnotationUtilsTests { @@ -210,8 +210,7 @@ public class AnnotationUtilsTests {
/** @since 4.1.2 */
@Test
public void findClassAnnotationFavorsMoreLocallyDeclaredComposedAnnotationsOverAnnotationsOnInterfaces() {
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class,
Component.class);
Component component = findAnnotation(ClassWithLocalMetaAnnotationAndMetaAnnotatedInterface.class, Component.class);
assertNotNull(component);
assertEquals("meta2", component.value());
}
@ -552,7 +551,7 @@ public class AnnotationUtilsTests { @@ -552,7 +551,7 @@ public class AnnotationUtilsTests {
@Test
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute [value] in"));
exception.expectMessage(startsWith("Attribute 'value' in"));
exception.expectMessage(containsString(BrokenContextConfig.class.getName()));
exception.expectMessage(containsString("@AliasFor [location]"));
@ -844,7 +843,7 @@ public class AnnotationUtilsTests { @@ -844,7 +843,7 @@ public class AnnotationUtilsTests {
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("@AliasFor declaration on attribute [foo] in annotation"));
exception.expectMessage(startsWith("@AliasFor declaration on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithMissingAttributeDeclaration.class.getName()));
exception.expectMessage(containsString("points to itself"));
synthesizeAnnotation(annotation);
@ -854,7 +853,7 @@ public class AnnotationUtilsTests { @@ -854,7 +853,7 @@ public class AnnotationUtilsTests {
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("In @AliasFor declared on attribute [foo] in annotation"));
exception.expectMessage(startsWith("In @AliasFor declared on attribute 'foo' in annotation"));
exception.expectMessage(containsString(AliasForWithDuplicateAttributeDeclaration.class.getName()));
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
synthesizeAnnotation(annotation);
@ -864,9 +863,9 @@ public class AnnotationUtilsTests { @@ -864,9 +863,9 @@ public class AnnotationUtilsTests {
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("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]"));
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute 'bar'"));
synthesizeAnnotation(annotation);
}
@ -875,7 +874,7 @@ public class AnnotationUtilsTests { @@ -875,7 +874,7 @@ public class AnnotationUtilsTests {
AliasForWithoutMirroredAliasFor annotation =
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Attribute [bar] in"));
exception.expectMessage(startsWith("Attribute 'bar' in"));
exception.expectMessage(containsString(AliasForWithoutMirroredAliasFor.class.getName()));
exception.expectMessage(containsString("@AliasFor [foo]"));
synthesizeAnnotation(annotation);
@ -887,10 +886,10 @@ public class AnnotationUtilsTests { @@ -887,10 +886,10 @@ public class AnnotationUtilsTests {
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("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]")));
or(containsString("is declared as an @AliasFor nonexistent attribute 'quux'")));
synthesizeAnnotation(annotation);
}
@ -901,8 +900,8 @@ public class AnnotationUtilsTests { @@ -901,8 +900,8 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
exception.expectMessage(containsString("attribute [foo]"));
exception.expectMessage(containsString("attribute [bar]"));
exception.expectMessage(containsString("attribute 'foo'"));
exception.expectMessage(containsString("attribute 'bar'"));
exception.expectMessage(containsString("same return type"));
synthesizeAnnotation(annotation);
}
@ -914,8 +913,8 @@ public class AnnotationUtilsTests { @@ -914,8 +913,8 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
exception.expectMessage(containsString("attribute [foo] in annotation"));
exception.expectMessage(containsString("attribute [bar] in annotation"));
exception.expectMessage(containsString("attribute 'foo' in annotation"));
exception.expectMessage(containsString("attribute 'bar' in annotation"));
exception.expectMessage(containsString("default values"));
synthesizeAnnotation(annotation);
}
@ -927,8 +926,8 @@ public class AnnotationUtilsTests { @@ -927,8 +926,8 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases"));
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
exception.expectMessage(containsString("attribute [foo] in annotation"));
exception.expectMessage(containsString("attribute [bar] in annotation"));
exception.expectMessage(containsString("attribute 'foo' in annotation"));
exception.expectMessage(containsString("attribute 'bar' in annotation"));
exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(annotation);
}
@ -938,9 +937,9 @@ public class AnnotationUtilsTests { @@ -938,9 +937,9 @@ public class AnnotationUtilsTests {
AliasedComposedContextConfigNotMetaPresent annotation =
AliasedComposedContextConfigNotMetaPresentClass.class.getAnnotation(AliasedComposedContextConfigNotMetaPresent.class);
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("@AliasFor declaration on attribute [xmlConfigFile] in annotation"));
exception.expectMessage(startsWith("@AliasFor declaration on attribute 'xmlConfigFile' in annotation"));
exception.expectMessage(containsString(AliasedComposedContextConfigNotMetaPresent.class.getName()));
exception.expectMessage(containsString("declares an alias for attribute [location] in meta-annotation"));
exception.expectMessage(containsString("declares an alias for attribute 'location' in meta-annotation"));
exception.expectMessage(containsString(ContextConfig.class.getName()));
exception.expectMessage(containsString("not meta-present"));
synthesizeAnnotation(annotation);
@ -1038,8 +1037,8 @@ public class AnnotationUtilsTests { @@ -1038,8 +1037,8 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("default values"));
synthesizeAnnotation(config, clazz);
@ -1054,8 +1053,8 @@ public class AnnotationUtilsTests { @@ -1054,8 +1053,8 @@ public class AnnotationUtilsTests {
exception.expect(AnnotationConfigurationException.class);
exception.expectMessage(startsWith("Misconfigured aliases:"));
exception.expectMessage(containsString("attribute [location1] in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute [location2] in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location1' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("attribute 'location2' in annotation [" + annotationType.getName() + "]"));
exception.expectMessage(containsString("same default value"));
synthesizeAnnotation(config, clazz);
@ -1220,7 +1219,7 @@ public class AnnotationUtilsTests { @@ -1220,7 +1219,7 @@ public class AnnotationUtilsTests {
private void assertMissingTextAttribute(Map<String, Object> attributes) {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned null for required attribute [text]"));
exception.expectMessage(containsString("returned null for required attribute 'text'"));
exception.expectMessage(containsString("defined by annotation type [" + AnnotationWithoutDefaults.class.getName() + "]"));
synthesizeAnnotation(attributes, AnnotationWithoutDefaults.class, null);
}
@ -1232,7 +1231,7 @@ public class AnnotationUtilsTests { @@ -1232,7 +1231,7 @@ public class AnnotationUtilsTests {
exception.expect(IllegalArgumentException.class);
exception.expectMessage(startsWith("Attributes map"));
exception.expectMessage(containsString("returned a value of type [java.lang.Long]"));
exception.expectMessage(containsString("for attribute [value]"));
exception.expectMessage(containsString("for attribute 'value'"));
exception.expectMessage(containsString("but a value of type [java.lang.String] is required"));
exception.expectMessage(containsString("as defined by annotation type [" + Component.class.getName() + "]"));
@ -1241,7 +1240,6 @@ public class AnnotationUtilsTests { @@ -1241,7 +1240,6 @@ public class AnnotationUtilsTests {
@Test
public void synthesizeAnnotationFromAnnotationAttributesWithoutAttributeAliases() throws Exception {
// 1) Get an annotation
Component component = WebController.class.getAnnotation(Component.class);
assertNotNull(component);

Loading…
Cancel
Save