Introduce getAliasedStringArray() in AnnotationAttributes
Issue: SPR-11393
This commit is contained in:
parent
08c032d9fd
commit
e5dc6e964c
|
@ -30,7 +30,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|||
import org.springframework.beans.factory.support.BeanNameGenerator;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter;
|
||||
|
@ -41,7 +40,6 @@ import org.springframework.core.type.filter.RegexPatternTypeFilter;
|
|||
import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -117,7 +115,8 @@ class ComponentScanAnnotationParser {
|
|||
}
|
||||
|
||||
Set<String> basePackages = new LinkedHashSet<String>();
|
||||
for (String pkg : getBasePackages(componentScan, declaringClass)) {
|
||||
String[] basePackagesArray = componentScan.getAliasedStringArray("basePackages", ComponentScan.class, declaringClass);
|
||||
for (String pkg : basePackagesArray) {
|
||||
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
|
||||
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
|
||||
basePackages.addAll(Arrays.asList(tokenized));
|
||||
|
@ -138,25 +137,6 @@ class ComponentScanAnnotationParser {
|
|||
return scanner.doScan(StringUtils.toStringArray(basePackages));
|
||||
}
|
||||
|
||||
private String[] getBasePackages(AnnotationAttributes componentScan, String declaringClass) {
|
||||
String[] value = componentScan.getStringArray("value");
|
||||
String[] basePackages = componentScan.getStringArray("basePackages");
|
||||
boolean valueDeclared = !ObjectUtils.isEmpty(value);
|
||||
boolean basePackagesDeclared = !ObjectUtils.isEmpty(basePackages);
|
||||
|
||||
if (valueDeclared && basePackagesDeclared && !ObjectUtils.nullSafeEquals(value, basePackages)) {
|
||||
String msg = String.format("In @ComponentScan declared on [%s], attribute [value] "
|
||||
+ "and its alias [basePackages] are present with values of [%s] and [%s], "
|
||||
+ "but only one is permitted.", declaringClass, ObjectUtils.nullSafeToString(value),
|
||||
ObjectUtils.nullSafeToString(basePackages));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
if (!basePackagesDeclared) {
|
||||
basePackages = value;
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
|
||||
private List<TypeFilter> typeFiltersFor(AnnotationAttributes filterAttributes) {
|
||||
List<TypeFilter> typeFilters = new ArrayList<TypeFilter>();
|
||||
FilterType filterType = filterAttributes.getEnum("type");
|
||||
|
|
|
@ -17,12 +17,14 @@
|
|||
package org.springframework.core.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -131,6 +133,58 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||
return doGet(attributeName, String[].class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value stored under the specified {@code attributeName} as an
|
||||
* array of strings, taking into account alias semantics defined via
|
||||
* {@link AliasFor @AliasFor}.
|
||||
* <p>If there is no value stored under the specified {@code attributeName}
|
||||
* but the attribute has an alias declared via {@code @AliasFor}, the
|
||||
* value of the alias will be returned.
|
||||
*
|
||||
* @param attributeName the name of the attribute to get; never
|
||||
* {@code null} or empty
|
||||
* @param annotationType the type of annotation represented by this
|
||||
* {@code AnnotationAttributes} instance; never {@code null}
|
||||
* @param annotationSource the source of the annotation represented by
|
||||
* this {@code AnnotationAttributes} (e.g., the {@link AnnotatedElement});
|
||||
* or {@code null} if unknown
|
||||
* @return the array of strings
|
||||
* @throws IllegalArgumentException if the attribute and its alias do
|
||||
* not exist or are not of type {@code String[]}
|
||||
* @throws AnnotationConfigurationException if the attribute and its
|
||||
* alias are both present with different non-empty values
|
||||
* @since 4.2
|
||||
*/
|
||||
public String[] getAliasedStringArray(String attributeName, Class<? extends Annotation> annotationType,
|
||||
Object annotationSource) {
|
||||
|
||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
||||
Assert.notNull(annotationType, "annotationType must not be null");
|
||||
|
||||
String[] attributeValue = getStringArrayWithoutNullCheck(attributeName);
|
||||
String aliasName = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
|
||||
String[] aliasValue = getStringArrayWithoutNullCheck(aliasName);
|
||||
boolean attributeDeclared = !ObjectUtils.isEmpty(attributeValue);
|
||||
boolean aliasDeclared = !ObjectUtils.isEmpty(aliasValue);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) && attributeDeclared && aliasDeclared) {
|
||||
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
|
||||
String msg = String.format("In annotation [%s] declared on [%s], "
|
||||
+ "attribute [%s] and its alias [%s] are present with values of [%s] and [%s], "
|
||||
+ "but only one is permitted.", this.displayName, elementName, attributeName, aliasName,
|
||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (!attributeDeclared) {
|
||||
attributeValue = aliasValue;
|
||||
}
|
||||
|
||||
assertAttributePresence(attributeName, aliasName, attributeValue);
|
||||
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value stored under the specified {@code attributeName} as a
|
||||
* boolean.
|
||||
|
@ -284,29 +338,52 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||
* @return the value
|
||||
* @throws IllegalArgumentException if the attribute does not exist or
|
||||
* if it is not of the expected type
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T doGet(String attributeName, Class<T> expectedType) {
|
||||
Assert.hasText(attributeName, "attributeName must not be null or empty");
|
||||
Object value = get(attributeName);
|
||||
if (value == null) {
|
||||
assertAttributePresence(attributeName, value);
|
||||
if (!expectedType.isInstance(value) && expectedType.isArray()
|
||||
&& expectedType.getComponentType().isInstance(value)) {
|
||||
Object array = Array.newInstance(expectedType.getComponentType(), 1);
|
||||
Array.set(array, 0, value);
|
||||
value = array;
|
||||
}
|
||||
assertAttributeType(attributeName, value, expectedType);
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
private void assertAttributePresence(String attributeName, Object attributeValue) {
|
||||
if (attributeValue == null) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute '%s' not found in attributes for annotation [%s]", attributeName, this.displayName));
|
||||
}
|
||||
if (!expectedType.isInstance(value)) {
|
||||
if (expectedType.isArray() && expectedType.getComponentType().isInstance(value)) {
|
||||
Object array = Array.newInstance(expectedType.getComponentType(), 1);
|
||||
Array.set(array, 0, value);
|
||||
value = array;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
|
||||
attributeName, value.getClass().getSimpleName(), expectedType.getSimpleName(), this.displayName));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertAttributePresence(String attributeName, String aliasName, Object attributeValue) {
|
||||
if (attributeValue == null) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Neither attribute '%s' nor its alias '%s' was found in attributes for annotation [%s]", attributeName,
|
||||
aliasName, this.displayName));
|
||||
}
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
private void assertAttributeType(String attributeName, Object attributeValue, Class<?> expectedType) {
|
||||
if (!expectedType.isInstance(attributeValue)) {
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"Attribute '%s' is of type [%s], but [%s] was expected in attributes for annotation [%s]",
|
||||
attributeName, attributeValue.getClass().getSimpleName(), expectedType.getSimpleName(),
|
||||
this.displayName));
|
||||
}
|
||||
}
|
||||
|
||||
private String[] getStringArrayWithoutNullCheck(String attributeName) {
|
||||
Object value = get(attributeName);
|
||||
if (value != null) {
|
||||
assertAttributeType(attributeName, value, String[].class);
|
||||
}
|
||||
return (String[]) value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -354,7 +431,6 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an {@link AnnotationAttributes} instance based on the given map.
|
||||
* <p>If the map is already an {@code AnnotationAttributes} instance, it
|
||||
|
|
|
@ -144,6 +144,71 @@ public class AnnotationAttributesTests {
|
|||
attributes.getEnum("color");
|
||||
}
|
||||
|
||||
@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"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", INPUT);
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", INPUT);
|
||||
attributes.put("value", EMPTY);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", EMPTY);
|
||||
attributes.put("value", INPUT);
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||
|
||||
attributes.clear();
|
||||
attributes.put("locations", EMPTY);
|
||||
attributes.put("value", EMPTY);
|
||||
assertArrayEquals(EMPTY, getAliasedStringArray("locations"));
|
||||
assertArrayEquals(EMPTY, getAliasedStringArray("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");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAliasedStringArrayWithDifferentAliasedValues() {
|
||||
attributes.put("locations", new String[] { "1.xml" });
|
||||
attributes.put("value", new String[] { "2.xml" });
|
||||
|
||||
exception.expect(AnnotationConfigurationException.class);
|
||||
exception.expectMessage(containsString("In annotation [unknown]"));
|
||||
exception.expectMessage(containsString("attribute [locations] and its alias [value]"));
|
||||
exception.expectMessage(containsString("[{1.xml}] and [{2.xml}]"));
|
||||
exception.expectMessage(containsString("but only one is permitted"));
|
||||
|
||||
getAliasedStringArray("locations");
|
||||
}
|
||||
|
||||
private String[] getAliasedStringArray(String attributeName) {
|
||||
return attributes.getAliasedStringArray(attributeName, ContextConfig.class, null);
|
||||
}
|
||||
|
||||
|
||||
enum Color {
|
||||
RED, WHITE, BLUE
|
||||
|
@ -157,4 +222,18 @@ public class AnnotationAttributesTests {
|
|||
@Filter(pattern = "foo")
|
||||
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 "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue