Skip to content

Commit 8ff9e81

Browse files
committed
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
1 parent b9fe6d8 commit 8ff9e81

File tree

4 files changed

+49
-50
lines changed

4 files changed

+49
-50
lines changed

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -1953,7 +1953,7 @@ private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
19531953
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
19541954
if (this.aliasedAnnotationType == this.sourceAnnotationType &&
19551955
this.aliasedAttributeName.equals(this.sourceAttributeName)) {
1956-
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] points to " +
1956+
String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
19571957
"itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
19581958
sourceAttribute.getName(), declaringClass.getName());
19591959
throw new AnnotationConfigurationException(msg);
@@ -1963,7 +1963,7 @@ private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
19631963
}
19641964
catch (NoSuchMethodException ex) {
19651965
String msg = String.format(
1966-
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
1966+
"Attribute '%s' in annotation [%s] is declared as an @AliasFor nonexistent attribute '%s' in annotation [%s].",
19671967
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
19681968
this.aliasedAnnotationType.getName());
19691969
throw new AnnotationConfigurationException(msg, ex);
@@ -1975,8 +1975,8 @@ private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
19751975
private void validate() {
19761976
// Target annotation is not meta-present?
19771977
if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
1978-
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares " +
1979-
"an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
1978+
String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] declares " +
1979+
"an alias for attribute '%s' in meta-annotation [%s] which is not meta-present.",
19801980
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
19811981
this.aliasedAnnotationType.getName());
19821982
throw new AnnotationConfigurationException(msg);
@@ -1985,14 +1985,14 @@ private void validate() {
19851985
if (this.isAliasPair) {
19861986
AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
19871987
if (mirrorAliasFor == null) {
1988-
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
1988+
String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s].",
19891989
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
19901990
throw new AnnotationConfigurationException(msg);
19911991
}
19921992

19931993
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, this.aliasedAttribute);
19941994
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
1995-
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
1995+
String msg = String.format("Attribute '%s' in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
19961996
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
19971997
mirrorAliasedAttributeName);
19981998
throw new AnnotationConfigurationException(msg);
@@ -2001,9 +2001,10 @@ private void validate() {
20012001

20022002
Class<?> returnType = this.sourceAttribute.getReturnType();
20032003
Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
2004-
if (returnType != aliasedReturnType) {
2005-
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
2006-
"and attribute [%s] in annotation [%s] must declare the same return type.",
2004+
if (returnType != aliasedReturnType &&
2005+
(!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
2006+
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
2007+
"and attribute '%s' in annotation [%s] must declare the same return type.",
20072008
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
20082009
this.aliasedAnnotationType.getName());
20092010
throw new AnnotationConfigurationException(msg);
@@ -2020,16 +2021,16 @@ private void validateDefaultValueConfiguration(Method aliasedAttribute) {
20202021
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
20212022

20222023
if (defaultValue == null || aliasedDefaultValue == null) {
2023-
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
2024-
"and attribute [%s] in annotation [%s] must declare default values.",
2024+
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
2025+
"and attribute '%s' in annotation [%s] must declare default values.",
20252026
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
20262027
aliasedAttribute.getDeclaringClass().getName());
20272028
throw new AnnotationConfigurationException(msg);
20282029
}
20292030

20302031
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
2031-
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
2032-
"and attribute [%s] in annotation [%s] must declare the same default value.",
2032+
String msg = String.format("Misconfigured aliases: attribute '%s' in annotation [%s] " +
2033+
"and attribute '%s' in annotation [%s] must declare the same default value.",
20332034
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
20342035
aliasedAttribute.getDeclaringClass().getName());
20352036
throw new AnnotationConfigurationException(msg);
@@ -2154,7 +2155,7 @@ private String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
21542155

21552156
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
21562157
if (attributeDeclared && valueDeclared) {
2157-
String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " +
2158+
String msg = String.format("In @AliasFor declared on attribute '%s' in annotation [%s], attribute 'attribute' " +
21582159
"and its alias 'value' are present with values of [%s] and [%s], but only one is permitted.",
21592160
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value);
21602161
throw new AnnotationConfigurationException(msg);

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.AnnotatedElement;
2121
import java.lang.reflect.Method;
22-
import java.util.HashMap;
22+
import java.util.LinkedHashMap;
2323
import java.util.List;
2424
import java.util.Map;
2525

@@ -87,7 +87,7 @@ protected Object getRawAttributeValue(String attributeName) {
8787
private static Map<String, Object> enrichAndValidateAttributes(
8888
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
8989

90-
Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
90+
Map<String, Object> attributes = new LinkedHashMap<String, Object>(originalAttributes);
9191
Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
9292

9393
for (Method attributeMethod : getAttributeMethods(annotationType)) {
@@ -121,7 +121,7 @@ private static Map<String, Object> enrichAndValidateAttributes(
121121
// if still null
122122
if (attributeValue == null) {
123123
throw new IllegalArgumentException(String.format(
124-
"Attributes map [%s] returned null for required attribute [%s] defined by annotation type [%s].",
124+
"Attributes map %s returned null for required attribute '%s' defined by annotation type [%s].",
125125
attributes, attributeName, annotationType.getName()));
126126
}
127127

@@ -155,8 +155,8 @@ else if (requiredReturnType.isArray() && actualReturnType.isArray() &&
155155

156156
if (!converted) {
157157
throw new IllegalArgumentException(String.format(
158-
"Attributes map [%s] returned a value of type [%s] for attribute [%s], "
159-
+ "but a value of type [%s] is required as defined by annotation type [%s].",
158+
"Attributes map %s returned a value of type [%s] for attribute '%s', " +
159+
"but a value of type [%s] is required as defined by annotation type [%s].",
160160
attributes, actualReturnType.getName(), attributeName, requiredReturnType.getName(),
161161
annotationType.getName()));
162162
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -959,7 +959,7 @@ static class MetaCycleAnnotatedClass {
959959
@interface TestComponentScan {
960960

961961
@AliasFor(attribute = "basePackages", annotation = ComponentScan.class)
962-
String[] packages();
962+
String pkg();
963963
}
964964

965965
// -------------------------------------------------------------------------
@@ -1152,7 +1152,7 @@ static class AliasedComposedContextConfigAndTestPropSourceClass {
11521152
static class ComponentScanWithBasePackagesAndValueAliasClass {
11531153
}
11541154

1155-
@TestComponentScan(packages = "com.example.app.test")
1155+
@TestComponentScan(pkg = "com.example.app.test")
11561156
static class TestComponentScanClass {
11571157
}
11581158

0 commit comments

Comments
 (0)