Merge from sbrannen/SPR-13345
* SPR-13345: Support implicit attribute aliases with @AliasFor
This commit is contained in:
commit
3eacb837c2
|
|
@ -19,6 +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.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -44,7 +45,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
|||
|
||||
private final S source;
|
||||
|
||||
private final Map<String, String> attributeAliasMap;
|
||||
private final Map<String, List<String>> attributeAliasMap;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -83,29 +84,33 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
|||
|
||||
@Override
|
||||
public final Object getAttributeValue(Method attributeMethod) {
|
||||
String attributeName = attributeMethod.getName();
|
||||
final String attributeName = attributeMethod.getName();
|
||||
Object attributeValue = getRawAttributeValue(attributeMethod);
|
||||
|
||||
String aliasName = this.attributeAliasMap.get(attributeName);
|
||||
if (aliasName != null) {
|
||||
Object aliasValue = getRawAttributeValue(aliasName);
|
||||
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
|
||||
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
|
||||
if (aliasNames != null) {
|
||||
final Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
|
||||
for (String aliasName : aliasNames) {
|
||||
if (aliasName != null) {
|
||||
Object aliasValue = getRawAttributeValue(aliasName);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
||||
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
||||
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
|
||||
String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
|
||||
throw new AnnotationConfigurationException(String.format(
|
||||
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
|
||||
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
|
||||
getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
|
||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
|
||||
}
|
||||
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
||||
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
||||
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
|
||||
String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
|
||||
throw new AnnotationConfigurationException(String.format(
|
||||
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
|
||||
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
|
||||
getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
|
||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
|
||||
}
|
||||
|
||||
// If the user didn't declare the annotation with an explicit value,
|
||||
// return the value of the alias.
|
||||
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
||||
attributeValue = aliasValue;
|
||||
// If the user didn't declare the annotation with an explicit value,
|
||||
// use the value of the alias instead.
|
||||
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
||||
attributeValue = aliasValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ import java.lang.annotation.Target;
|
|||
*
|
||||
* <h3>Usage Scenarios</h3>
|
||||
* <ul>
|
||||
* <li><strong>Aliases within an annotation</strong>: within a single
|
||||
* <li><strong>Explicit aliases within an annotation</strong>: within a single
|
||||
* annotation, {@code @AliasFor} can be declared on a pair of attributes to
|
||||
* signal that they are interchangeable aliases for each other.</li>
|
||||
* <li><strong>Alias for attribute in meta-annotation</strong>: if the
|
||||
* <li><strong>Explicit alias for attribute in meta-annotation</strong>: if the
|
||||
* {@link #annotation} attribute of {@code @AliasFor} is set to a different
|
||||
* annotation than the one that declares it, the {@link #attribute} is
|
||||
* interpreted as an alias for an attribute in a meta-annotation (i.e., an
|
||||
|
|
@ -40,6 +40,11 @@ import java.lang.annotation.Target;
|
|||
* control over exactly which attributes are overridden within an annotation
|
||||
* hierarchy. In fact, with {@code @AliasFor} it is even possible to declare
|
||||
* an alias for the {@code value} attribute of a meta-annotation.</li>
|
||||
* <li><strong>Implicit aliases within an annotation</strong>: if one or
|
||||
* more attributes within an annotation are declared as explicit
|
||||
* meta-annotation attribute overrides for the same attribute in the
|
||||
* meta-annotation, those attributes will be treated as a set of <em>implicit</em>
|
||||
* aliases for each other, analogous to explicit aliases within an annotation.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Usage Requirements</h3>
|
||||
|
|
@ -57,31 +62,44 @@ import java.lang.annotation.Target;
|
|||
*
|
||||
* <h3>Implementation Requirements</h3>
|
||||
* <ul>
|
||||
* <li><strong>Aliases within an annotation</strong>:
|
||||
* <li><strong>Explicit aliases within an annotation</strong>:
|
||||
* <ol>
|
||||
* <li>Each attribute that makes up an aliased pair must be annotated with
|
||||
* {@code @AliasFor}, and either the {@link #attribute} or the {@link #value}
|
||||
* attribute must reference the <em>other</em> attribute in the pair.</li>
|
||||
* {@code @AliasFor}, and either {@link #attribute} or {@link #value} must
|
||||
* reference the <em>other</em> attribute in the pair.</li>
|
||||
* <li>Aliased attributes must declare the same return type.</li>
|
||||
* <li>Aliased attributes must declare a default value.</li>
|
||||
* <li>Aliased attributes must declare the same default value.</li>
|
||||
* <li>The {@link #annotation} attribute should remain set to the default.</li>
|
||||
* <li>{@link #annotation} should not be declared.</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* <li><strong>Alias for attribute in meta-annotation</strong>:
|
||||
* <li><strong>Explicit alias for attribute in meta-annotation</strong>:
|
||||
* <ol>
|
||||
* <li>The attribute that is an alias for an attribute in a meta-annotation
|
||||
* must be annotated with {@code @AliasFor}, and the {@link #attribute} must
|
||||
* reference the aliased attribute in the meta-annotation.</li>
|
||||
* must be annotated with {@code @AliasFor}, and {@link #attribute} must
|
||||
* reference the attribute in the meta-annotation.</li>
|
||||
* <li>Aliased attributes must declare the same return type.</li>
|
||||
* <li>The {@link #annotation} must reference the meta-annotation.</li>
|
||||
* <li>{@link #annotation} must reference the meta-annotation.</li>
|
||||
* <li>The referenced meta-annotation must be <em>meta-present</em> on the
|
||||
* annotation class that declares {@code @AliasFor}.</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* <li><strong>Implicit aliases within an annotation</strong>:
|
||||
* <ol>
|
||||
* <li>Each attribute that belongs to the set of implicit aliases must be
|
||||
* annotated with {@code @AliasFor}, and {@link #attribute} must reference
|
||||
* the same attribute in the same meta-annotation.</li>
|
||||
* <li>Aliased attributes must declare the same return type.</li>
|
||||
* <li>Aliased attributes must declare a default value.</li>
|
||||
* <li>Aliased attributes must declare the same default value.</li>
|
||||
* <li>{@link #annotation} must reference the meta-annotation.</li>
|
||||
* <li>The referenced meta-annotation must be <em>meta-present</em> on the
|
||||
* annotation class that declares {@code @AliasFor}.</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <h3>Example: Aliases within an Annotation</h3>
|
||||
* <h3>Example: Explicit Aliases within an Annotation</h3>
|
||||
* <pre class="code"> public @interface ContextConfiguration {
|
||||
*
|
||||
* @AliasFor("locations")
|
||||
|
|
@ -93,7 +111,7 @@ import java.lang.annotation.Target;
|
|||
* // ...
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Example: Alias for Attribute in Meta-annotation</h3>
|
||||
* <h3>Example: Explicit Alias for Attribute in Meta-annotation</h3>
|
||||
* <pre class="code"> @ContextConfiguration
|
||||
* public @interface MyTestConfig {
|
||||
*
|
||||
|
|
@ -101,6 +119,20 @@ import java.lang.annotation.Target;
|
|||
* String[] xmlFiles();
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Example: Implicit Aliases within an Annotation</h3>
|
||||
* <pre class="code"> @ContextConfiguration
|
||||
* public @interface MyTestConfig {
|
||||
*
|
||||
* @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
|
||||
* String[] value() default {};
|
||||
*
|
||||
* @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
|
||||
* String[] groovyScripts() default {};
|
||||
*
|
||||
* @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
|
||||
* String[] xmlFiles() default {};
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Spring Annotations Supporting Attribute Aliases</h3>
|
||||
* <p>As of Spring Framework 4.2, several annotations within core Spring
|
||||
* have been updated to use {@code @AliasFor} to configure their internal
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import org.springframework.core.BridgeMethodResolver;
|
|||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* General utility methods for finding annotations and meta-annotations on
|
||||
|
|
@ -957,13 +956,21 @@ public class AnnotatedElementUtils {
|
|||
|
||||
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
|
||||
String attributeName = attributeMethod.getName();
|
||||
String aliasedAttributeName = AnnotationUtils.getAliasedAttributeName(attributeMethod,
|
||||
targetAnnotationType);
|
||||
List<String> aliases = AnnotationUtils.getAliasedAttributeNames(attributeMethod, targetAnnotationType);
|
||||
|
||||
// Explicit annotation attribute override declared via @AliasFor
|
||||
if (StringUtils.hasText(aliasedAttributeName) && attributes.containsKey(aliasedAttributeName)) {
|
||||
overrideAttribute(element, annotation, attributes, attributeName, aliasedAttributeName);
|
||||
if (!aliases.isEmpty()) {
|
||||
if (aliases.size() != 1) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Alias list for annotation attribute [%s] must contain at most one element: %s",
|
||||
attributeMethod, aliases));
|
||||
}
|
||||
String aliasedAttributeName = aliases.get(0);
|
||||
if (attributes.containsKey(aliasedAttributeName)) {
|
||||
overrideAttribute(element, annotation, attributes, attributeName, aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
// Implicit annotation attribute override based on convention
|
||||
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
|
||||
overrideAttribute(element, annotation, attributes, attributeName, attributeName);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement;
|
|||
import java.lang.reflect.Array;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
|
@ -422,26 +423,38 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||
Assert.notNull(expectedType, "expectedType must not be null");
|
||||
|
||||
T attributeValue = getAttribute(attributeName, expectedType);
|
||||
String aliasName = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
|
||||
T aliasValue = getAttribute(aliasName, expectedType);
|
||||
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.",
|
||||
annotationType.getName(), elementName, attributeName, aliasName,
|
||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
List<String> aliasNames = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
|
||||
if (aliasNames != null) {
|
||||
for (String aliasName : aliasNames) {
|
||||
T aliasValue = getAttribute(aliasName, expectedType);
|
||||
boolean attributeEmpty = ObjectUtils.isEmpty(attributeValue);
|
||||
boolean aliasEmpty = ObjectUtils.isEmpty(aliasValue);
|
||||
|
||||
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
|
||||
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.",
|
||||
annotationType.getName(), elementName, attributeName, aliasName,
|
||||
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
// If we expect an array and the current tracked value is null but the
|
||||
// current alias value is non-null, then replace the current null value
|
||||
// with the non-null value (which may be an empty array).
|
||||
if (expectedType.isArray() && attributeValue == null && aliasValue != null) {
|
||||
attributeValue = aliasValue;
|
||||
}
|
||||
// Else: if we're not expecting an array, we can rely on the behavior of
|
||||
// ObjectUtils.isEmpty().
|
||||
else if (attributeEmpty && !aliasEmpty) {
|
||||
attributeValue = aliasValue;
|
||||
}
|
||||
}
|
||||
assertAttributePresence(attributeName, aliasNames, attributeValue);
|
||||
}
|
||||
|
||||
if (!attributeDeclared) {
|
||||
attributeValue = aliasValue;
|
||||
}
|
||||
|
||||
assertAttributePresence(attributeName, aliasName, attributeValue);
|
||||
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
|
|
@ -473,11 +486,11 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
|||
}
|
||||
}
|
||||
|
||||
private void assertAttributePresence(String attributeName, String aliasName, Object attributeValue) {
|
||||
private void assertAttributePresence(String attributeName, List<String> aliases, 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));
|
||||
"Neither attribute '%s' nor one of its aliases %s was found in attributes for annotation [%s]",
|
||||
attributeName, aliases, this.displayName));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,9 @@ import org.springframework.util.StringUtils;
|
|||
*
|
||||
* <p>An annotation is <em>meta-present</em> on an element if the annotation
|
||||
* is declared as a meta-annotation on some other annotation which is
|
||||
* <em>present</em> on the element.
|
||||
* <em>present</em> on the element. Annotation {@code A} is <em>meta-present</em>
|
||||
* on another annotation if {@code A} is either <em>directly present</em> or
|
||||
* <em>meta-present</em> on the other annotation.
|
||||
*
|
||||
* <h3>Meta-annotation Support</h3>
|
||||
* <p>Most {@code find*()} methods and some {@code get*()} methods in this
|
||||
|
|
@ -123,11 +125,14 @@ public abstract class AnnotationUtils {
|
|||
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
|
||||
new ConcurrentReferenceHashMap<Class<?>, Boolean>(256);
|
||||
|
||||
private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
|
||||
new ConcurrentReferenceHashMap<AnnotationCacheKey, Boolean>(256);
|
||||
|
||||
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
|
||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Boolean>(256);
|
||||
|
||||
private static final Map<Class<? extends Annotation>, Map<String, String>> attributeAliasesCache =
|
||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, String>>(256);
|
||||
private static final Map<Class<? extends Annotation>, Map<String, List<String>>> attributeAliasesCache =
|
||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, List<String>>>(256);
|
||||
|
||||
private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache =
|
||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, List<Method>>(256);
|
||||
|
|
@ -643,8 +648,22 @@ public abstract class AnnotationUtils {
|
|||
* @param annotationType the type of annotation to look for
|
||||
* @return the first matching annotation, or {@code null} if not found
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
|
||||
return findAnnotation(clazz, annotationType, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the actual work for {@link #findAnnotation(AnnotatedElement, Class)},
|
||||
* honoring the {@code synthesize} flag.
|
||||
* @param clazz the class to look for annotations on; never {@code null}
|
||||
* @param annotationType the type of annotation to look for
|
||||
* @param synthesize {@code true} if the result should be
|
||||
* {@linkplain #synthesizeAnnotation(Annotation) synthesized}
|
||||
* @return the first matching annotation, or {@code null} if not found
|
||||
* @since 4.2.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, boolean synthesize) {
|
||||
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
|
||||
A result = (A) findAnnotationCache.get(cacheKey);
|
||||
if (result == null) {
|
||||
|
|
@ -653,7 +672,7 @@ public abstract class AnnotationUtils {
|
|||
findAnnotationCache.put(cacheKey, result);
|
||||
}
|
||||
}
|
||||
return synthesizeAnnotation(result, clazz);
|
||||
return (synthesize ? synthesizeAnnotation(result, clazz) : result);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -833,6 +852,30 @@ public abstract class AnnotationUtils {
|
|||
return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an annotation of type {@code metaAnnotationType} is
|
||||
* <em>meta-present</em> on the supplied {@code annotationType}.
|
||||
* @param annotationType the annotation type to search on; never {@code null}
|
||||
* @param metaAnnotationType the type of meta-annotation to search for
|
||||
* @return {@code true} if such an annotation is meta-present
|
||||
* @since 4.2.1
|
||||
*/
|
||||
public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
|
||||
Class<? extends Annotation> metaAnnotationType) {
|
||||
|
||||
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
|
||||
Boolean metaPresent = metaPresentCache.get(cacheKey);
|
||||
if (metaPresent != null) {
|
||||
return metaPresent.booleanValue();
|
||||
}
|
||||
metaPresent = Boolean.FALSE;
|
||||
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
|
||||
metaPresent = Boolean.TRUE;
|
||||
}
|
||||
metaPresentCache.put(cacheKey, metaPresent);
|
||||
return metaPresent.booleanValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Annotation} is defined in the core JDK
|
||||
* {@code java.lang.annotation} package.
|
||||
|
|
@ -1363,33 +1406,39 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get a map of all attribute alias pairs, declared via {@code @AliasFor}
|
||||
* Get a map of all attribute aliases declared via {@code @AliasFor}
|
||||
* in the supplied annotation type.
|
||||
* <p>The map is keyed by attribute name with each value representing
|
||||
* the name of the aliased attribute. For each entry {@code [x, y]} in
|
||||
* the map there will be a corresponding {@code [y, x]} entry in the map.
|
||||
* a list of names of aliased attributes.
|
||||
* <p>For <em>explicit</em> alias pairs such as x and y (i.e., where x
|
||||
* is an {@code @AliasFor("y")} and y is an {@code @AliasFor("x")}, there
|
||||
* will be two entries in the map: {@code x -> (y)} and {@code y -> (x)}.
|
||||
* <p>For <em>implicit</em> aliases (i.e., attributes that are declared
|
||||
* as attribute overrides for the same attribute in the same meta-annotation),
|
||||
* there will be n entries in the map. For example, if x, y, and z are
|
||||
* implicit aliases, the map will contain the following entries:
|
||||
* {@code x -> (y, z)}, {@code y -> (x, z)}, {@code z -> (x, y)}.
|
||||
* <p>An empty return value implies that the annotation does not declare
|
||||
* any attribute aliases.
|
||||
* @param annotationType the annotation type to find attribute aliases in
|
||||
* @return a map containing attribute alias pairs; never {@code null}
|
||||
* @return a map containing attribute aliases; never {@code null}
|
||||
* @since 4.2
|
||||
*/
|
||||
static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
|
||||
static Map<String, List<String>> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
|
||||
if (annotationType == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, String> map = attributeAliasesCache.get(annotationType);
|
||||
Map<String, List<String>> map = attributeAliasesCache.get(annotationType);
|
||||
if (map != null) {
|
||||
return map;
|
||||
}
|
||||
|
||||
map = new HashMap<String, String>();
|
||||
map = new HashMap<String, List<String>>();
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
String attributeName = attribute.getName();
|
||||
String aliasedAttributeName = getAliasedAttributeName(attribute);
|
||||
if (aliasedAttributeName != null) {
|
||||
map.put(attributeName, aliasedAttributeName);
|
||||
List<String> aliasNames = getAliasedAttributeNames(attribute);
|
||||
if (!aliasNames.isEmpty()) {
|
||||
map.put(attribute.getName(), aliasNames);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1420,7 +1469,7 @@ public abstract class AnnotationUtils {
|
|||
|
||||
synthesizable = Boolean.FALSE;
|
||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||
if (getAliasedAttributeName(attribute) != null) {
|
||||
if (!getAliasedAttributeNames(attribute).isEmpty()) {
|
||||
synthesizable = Boolean.TRUE;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1446,184 +1495,85 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via
|
||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||
* <p>This method does not resolve aliases in other annotations. In
|
||||
* other words, if {@code @AliasFor} is present on the supplied
|
||||
* {@code attribute} but {@linkplain AliasFor#annotation references an
|
||||
* annotation} other than {@link Annotation}, this method will return
|
||||
* {@code null} immediately.
|
||||
* @param attribute the attribute to find an alias for
|
||||
* @return the name of the aliased attribute, or {@code null} if not found
|
||||
* Get the names of the aliased attributes configured via
|
||||
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
|
||||
* <p>This method does not resolve meta-annotation attribute overrides.
|
||||
* @param attribute the attribute to find aliases for; never {@code null}
|
||||
* @return the names of the aliased attributes; never {@code null}, though
|
||||
* potentially <em>empty</em>
|
||||
* @throws IllegalArgumentException if the supplied attribute method is
|
||||
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||
* {@code null} or not from an annotation
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
* @see #getAliasedAttributeName(Method, Class)
|
||||
* @see #getAliasedAttributeNames(Method, Class)
|
||||
*/
|
||||
static String getAliasedAttributeName(Method attribute) {
|
||||
return getAliasedAttributeName(attribute, (Class<? extends Annotation>) null);
|
||||
static List<String> getAliasedAttributeNames(Method attribute) {
|
||||
return getAliasedAttributeNames(attribute, (Class<? extends Annotation>) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via
|
||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
||||
* @param attribute the attribute to find an alias for
|
||||
* @param targetAnnotationType the type of annotation in which the
|
||||
* Get the names of the aliased attributes configured via
|
||||
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
|
||||
* <p>If the supplied {@code metaAnnotationType} is non-null, the
|
||||
* returned list will contain at most one element.
|
||||
* @param attribute the attribute to find aliases for; never {@code null}
|
||||
* @param metaAnnotationType the type of meta-annotation in which an
|
||||
* aliased attribute is allowed to be declared; {@code null} implies
|
||||
* <em>within the same annotation</em>
|
||||
* @return the name of the aliased attribute, or {@code null} if not found
|
||||
* <em>within the same annotation</em> as the supplied attribute
|
||||
* @return the names of the aliased attributes; never {@code null}, though
|
||||
* potentially <em>empty</em>
|
||||
* @throws IllegalArgumentException if the supplied attribute method is
|
||||
* not from an annotation, or if the supplied target type is {@link Annotation}
|
||||
* {@code null} or not from an annotation, or if the supplied meta-annotation
|
||||
* type is {@link Annotation}
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static String getAliasedAttributeName(Method attribute, Class<? extends Annotation> targetAnnotationType) {
|
||||
Class<?> declaringClass = attribute.getDeclaringClass();
|
||||
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
|
||||
Assert.isTrue(!Annotation.class.equals(targetAnnotationType),
|
||||
"targetAnnotationType must not be java.lang.annotation.Annotation");
|
||||
static List<String> getAliasedAttributeNames(Method attribute, Class<? extends Annotation> metaAnnotationType) {
|
||||
Assert.notNull(attribute, "attribute method must not be null");
|
||||
Assert.isTrue(!Annotation.class.equals(metaAnnotationType),
|
||||
"metaAnnotationType must not be java.lang.annotation.Annotation");
|
||||
|
||||
String attributeName = attribute.getName();
|
||||
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
|
||||
AliasDescriptor descriptor = AliasDescriptor.from(attribute);
|
||||
|
||||
// Nothing to check
|
||||
if (aliasFor == null) {
|
||||
return null;
|
||||
// No alias declared via @AliasFor?
|
||||
if (descriptor == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Class<? extends Annotation> sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
|
||||
Class<? extends Annotation> aliasedAnnotationType = aliasFor.annotation();
|
||||
|
||||
boolean searchWithinSameAnnotation = (targetAnnotationType == null);
|
||||
boolean sameTargetDeclared =
|
||||
(sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType));
|
||||
|
||||
// Explicit alias for a different target meta-annotation?
|
||||
if (!searchWithinSameAnnotation && !targetAnnotationType.equals(aliasedAnnotationType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String aliasedAttributeName = getAliasedAttributeName(aliasFor, attribute);
|
||||
|
||||
if (!StringUtils.hasText(aliasedAttributeName)) {
|
||||
String msg = String.format(
|
||||
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
|
||||
attributeName, sourceAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (!sameTargetDeclared) {
|
||||
// Target annotation is not meta-present?
|
||||
if (findAnnotation(sourceAnnotationType, aliasedAnnotationType) == null) {
|
||||
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.",
|
||||
attributeName, sourceAnnotationType.getName(), aliasedAttributeName,
|
||||
aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
// Searching for explicit meta-annotation attribute override?
|
||||
if (metaAnnotationType != null) {
|
||||
if (descriptor.isAliasFor(metaAnnotationType)) {
|
||||
return Collections.singletonList(descriptor.aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
else {
|
||||
aliasedAnnotationType = sourceAnnotationType;
|
||||
// Else: explicit attribute override for a different meta-annotation
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Wrong search scope?
|
||||
if (searchWithinSameAnnotation && !sameTargetDeclared) {
|
||||
return null;
|
||||
// Explicit alias pair?
|
||||
if (descriptor.isAliasPair) {
|
||||
return Collections.singletonList(descriptor.aliasedAttributeName);
|
||||
}
|
||||
|
||||
Method aliasedAttribute;
|
||||
try {
|
||||
aliasedAttribute = aliasedAnnotationType.getDeclaredMethod(aliasedAttributeName);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
String msg = String.format(
|
||||
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
|
||||
attributeName, sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg, ex);
|
||||
}
|
||||
// Else: search for implicit aliases
|
||||
List<String> aliases = new ArrayList<String>();
|
||||
for (Method currentAttribute : getAttributeMethods(descriptor.sourceAnnotationType)) {
|
||||
|
||||
if (sameTargetDeclared) {
|
||||
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
|
||||
if (mirrorAliasFor == null) {
|
||||
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
|
||||
aliasedAttributeName, sourceAnnotationType.getName(), attributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
// An attribute cannot alias itself
|
||||
if (attribute.equals(currentAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, aliasedAttribute);
|
||||
if (!attributeName.equals(mirrorAliasedAttributeName)) {
|
||||
String msg = String.format(
|
||||
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
|
||||
aliasedAttributeName, sourceAnnotationType.getName(), attributeName, mirrorAliasedAttributeName);
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
// If two attributes override the same attribute in the same meta-annotation,
|
||||
// they are "implicit" aliases for each other.
|
||||
AliasDescriptor otherDescriptor = AliasDescriptor.from(currentAttribute);
|
||||
if (descriptor.equals(otherDescriptor)) {
|
||||
descriptor.validateAgainst(otherDescriptor);
|
||||
aliases.add(otherDescriptor.sourceAttributeName);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> returnType = attribute.getReturnType();
|
||||
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
|
||||
if (!returnType.equals(aliasedReturnType)) {
|
||||
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
|
||||
"and attribute [%s] in annotation [%s] must declare the same return type.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
if (sameTargetDeclared) {
|
||||
Object defaultValue = attribute.getDefaultValue();
|
||||
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.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.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.", attributeName,
|
||||
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return aliasedAttributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via the supplied
|
||||
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
|
||||
* <p>This method returns the value of either the {@code attribute}
|
||||
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
|
||||
* one of the attributes has been declared.
|
||||
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
|
||||
* the aliased attribute name
|
||||
* @param attribute the attribute that is annotated with {@code @AliasFor},
|
||||
* used solely for building an exception message
|
||||
* @return the name of the aliased attribute, potentially an empty string
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
* @see #getAliasedAttributeName(Method, Class)
|
||||
*/
|
||||
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
|
||||
String attributeName = aliasFor.attribute();
|
||||
String value = aliasFor.value();
|
||||
boolean attributeDeclared = StringUtils.hasText(attributeName);
|
||||
boolean valueDeclared = StringUtils.hasText(value);
|
||||
|
||||
if (attributeDeclared && valueDeclared) {
|
||||
throw new AnnotationConfigurationException(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));
|
||||
}
|
||||
|
||||
return (attributeDeclared ? attributeName : value);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1677,6 +1627,7 @@ public abstract class AnnotationUtils {
|
|||
* Determine if the supplied {@code method} is an annotation attribute method.
|
||||
* @param method the method to check
|
||||
* @return {@code true} if the method is an attribute method
|
||||
* @since 4.2
|
||||
*/
|
||||
static boolean isAttributeMethod(Method method) {
|
||||
return (method != null && method.getParameterTypes().length == 0 && method.getReturnType() != void.class);
|
||||
|
|
@ -1686,6 +1637,7 @@ public abstract class AnnotationUtils {
|
|||
* Determine if the supplied method is an "annotationType" method.
|
||||
* @return {@code true} if the method is an "annotationType" method
|
||||
* @see Annotation#annotationType()
|
||||
* @since 4.2
|
||||
*/
|
||||
static boolean isAnnotationTypeMethod(Method method) {
|
||||
return (method != null && method.getName().equals("annotationType") && method.getParameterTypes().length == 0);
|
||||
|
|
@ -1723,40 +1675,62 @@ public abstract class AnnotationUtils {
|
|||
|
||||
Class<? extends Annotation> annotationType = attributes.annotationType();
|
||||
|
||||
// Track which attribute values have already been replaced so that we can short
|
||||
// circuit the search algorithms.
|
||||
Set<String> valuesAlreadyReplaced = new HashSet<String>();
|
||||
|
||||
// Validate @AliasFor configuration
|
||||
Map<String, String> aliasMap = getAttributeAliasMap(annotationType);
|
||||
Set<String> validated = new HashSet<String>();
|
||||
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||
for (String attributeName : aliasMap.keySet()) {
|
||||
String aliasedAttributeName = aliasMap.get(attributeName);
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
boolean valuePresent = (value != null && value != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validated.add(attributeName) && validated.add(aliasedAttributeName)) {
|
||||
Object value = attributes.get(attributeName);
|
||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||
boolean aliasPresent = (aliasedValue != null && aliasedValue != DEFAULT_VALUE_PLACEHOLDER);
|
||||
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && (value != DEFAULT_VALUE_PLACEHOLDER)
|
||||
&& (aliasedValue != DEFAULT_VALUE_PLACEHOLDER)) {
|
||||
String elementAsString = (element == null ? "unknown element" : element.toString());
|
||||
String msg = String.format(
|
||||
"In AnnotationAttributes for annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
||||
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
||||
annotationType.getName(), elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
// Replace default values with aliased values...
|
||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
if (aliasedValue == DEFAULT_VALUE_PLACEHOLDER) {
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
// Something to validate or replace with an alias?
|
||||
if (valuePresent || aliasPresent) {
|
||||
if (valuePresent && aliasPresent) {
|
||||
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
|
||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
|
||||
String elementAsString = (element == null ? "unknown element" : element.toString());
|
||||
String msg = String.format("In AnnotationAttributes for annotation [%s] declared on [%s], "
|
||||
+ "attribute [%s] and its alias [%s] are declared with values of [%s] and [%s], "
|
||||
+ "but only one declaration is permitted.", annotationType.getName(),
|
||||
elementAsString, attributeName, aliasedAttributeName,
|
||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
else if (aliasPresent) {
|
||||
// Replace value with aliasedValue
|
||||
attributes.put(attributeName,
|
||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(attributeName);
|
||||
}
|
||||
else {
|
||||
// Replace aliasedValue with value
|
||||
attributes.put(aliasedAttributeName,
|
||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replace any remaining placeholders with actual default values
|
||||
for (String attributeName : attributes.keySet()) {
|
||||
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||
continue;
|
||||
}
|
||||
Object value = attributes.get(attributeName);
|
||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
||||
attributes.put(attributeName,
|
||||
|
|
@ -1933,4 +1907,248 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code AliasDescriptor} encapsulates the declaration of {@code @AliasFor}
|
||||
* on a given annotation attribute and includes support for validating
|
||||
* the configuration of aliases (both explicit and implicit).
|
||||
* @since 4.2.1
|
||||
*/
|
||||
private static class AliasDescriptor {
|
||||
|
||||
private final Method sourceAttribute;
|
||||
|
||||
private final Class<? extends Annotation> sourceAnnotationType;
|
||||
|
||||
private final String sourceAttributeName;
|
||||
|
||||
private final Class<? extends Annotation> aliasedAnnotationType;
|
||||
|
||||
private final String aliasedAttributeName;
|
||||
|
||||
private final boolean isAliasPair;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code AliasDescriptor} <em>from</em> the declaration
|
||||
* of {@code @AliasFor} on the supplied annotation attribute and
|
||||
* validate the configuration of {@code @AliasFor}.
|
||||
* @param attribute the annotation attribute that is annotated with
|
||||
* {@code @AliasFor}
|
||||
* @return a new alias descriptor, or {@code null} if the attribute
|
||||
* is not annotated with {@code @AliasFor}
|
||||
* @see #validateAgainst(AliasDescriptor)
|
||||
*/
|
||||
public static AliasDescriptor from(Method attribute) {
|
||||
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
|
||||
if (aliasFor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
AliasDescriptor descriptor = new AliasDescriptor(attribute, aliasFor);
|
||||
descriptor.validate();
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
|
||||
Class<?> declaringClass = sourceAttribute.getDeclaringClass();
|
||||
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
|
||||
|
||||
this.sourceAttribute = sourceAttribute;
|
||||
this.sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
|
||||
this.sourceAttributeName = this.sourceAttribute.getName();
|
||||
this.aliasedAnnotationType = (Annotation.class.equals(aliasFor.annotation()) ? this.sourceAnnotationType
|
||||
: aliasFor.annotation());
|
||||
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, this.sourceAttribute);
|
||||
this.isAliasPair = this.sourceAnnotationType.equals(this.aliasedAnnotationType);
|
||||
}
|
||||
|
||||
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.",
|
||||
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
|
||||
this.aliasedAnnotationType.getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
Method aliasedAttribute;
|
||||
try {
|
||||
aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
String msg = String.format(
|
||||
"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);
|
||||
}
|
||||
|
||||
if (this.isAliasPair) {
|
||||
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
|
||||
if (mirrorAliasFor == null) {
|
||||
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,
|
||||
aliasedAttribute);
|
||||
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Class<?> returnType = this.sourceAttribute.getReturnType();
|
||||
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
|
||||
if (!returnType.equals(aliasedReturnType)) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (this.isAliasPair) {
|
||||
validateDefaultValueConfiguration(aliasedAttribute);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDefaultValueConfiguration(Method aliasedAttribute) {
|
||||
Assert.notNull(aliasedAttribute, "aliasedAttribute must not be null");
|
||||
Object defaultValue = this.sourceAttribute.getDefaultValue();
|
||||
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.",
|
||||
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.",
|
||||
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
|
||||
aliasedAttribute.getDeclaringClass().getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this descriptor against the supplied descriptor.
|
||||
* <p>This method only validates the configuration of default values
|
||||
* for the two descriptors, since other aspects of the descriptors
|
||||
* were validated when the descriptors were created.
|
||||
*/
|
||||
public void validateAgainst(AliasDescriptor otherDescriptor) {
|
||||
validateDefaultValueConfiguration(otherDescriptor.sourceAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this descriptor represent an alias for an attribute in the
|
||||
* supplied {@code targetAnnotationType}?
|
||||
*/
|
||||
public boolean isAliasFor(Class<? extends Annotation> targetAnnotationType) {
|
||||
return targetAnnotationType.equals(this.aliasedAnnotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this descriptor is logically equal to the supplied
|
||||
* object.
|
||||
* <p>Two descriptors are considered equal if the aliases they
|
||||
* represent are from attributes in one annotation that alias the
|
||||
* same attribute in a given target annotation.
|
||||
*/
|
||||
public boolean equals(Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof AliasDescriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AliasDescriptor that = (AliasDescriptor) other;
|
||||
|
||||
if (!this.sourceAnnotationType.equals(that.sourceAnnotationType)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.aliasedAnnotationType.equals(that.aliasedAnnotationType)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.aliasedAttributeName.equals(that.aliasedAttributeName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.sourceAnnotationType.hashCode();
|
||||
result = 31 * result + this.aliasedAnnotationType.hashCode();
|
||||
result = 31 * result + this.aliasedAttributeName.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s: '%s' in @%s is an alias for '%s' in @%s", getClass().getSimpleName(),
|
||||
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
|
||||
(this.aliasedAnnotationType != null ? this.aliasedAnnotationType.getName() : null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the aliased attribute configured via the supplied
|
||||
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
|
||||
* <p>This method returns the value of either the {@code attribute}
|
||||
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
|
||||
* one of the attributes has been declared while simultaneously ensuring
|
||||
* that at least one of the attributes has been declared.
|
||||
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
|
||||
* the aliased attribute name; never {@code null}
|
||||
* @param attribute the attribute that is annotated with {@code @AliasFor},
|
||||
* used solely for building an exception message; never {@code null}
|
||||
* @return the name of the aliased attribute, never {@code null} or empty
|
||||
* @throws AnnotationConfigurationException if invalid configuration of
|
||||
* {@code @AliasFor} is detected
|
||||
* @since 4.2
|
||||
* @see AnnotationUtils#getAliasedAttributeNames(Method, Class)
|
||||
*/
|
||||
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
|
||||
String attributeName = aliasFor.attribute();
|
||||
String value = aliasFor.value();
|
||||
boolean attributeDeclared = StringUtils.hasText(attributeName);
|
||||
boolean valueDeclared = StringUtils.hasText(value);
|
||||
|
||||
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
|
||||
if (attributeDeclared && valueDeclared) {
|
||||
throw new AnnotationConfigurationException(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));
|
||||
}
|
||||
|
||||
attributeName = (attributeDeclared ? attributeName : value);
|
||||
|
||||
// Ensure user declared either 'value' or 'attribute' in @AliasFor
|
||||
if (!StringUtils.hasText(attributeName)) {
|
||||
String msg = String.format(
|
||||
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
|
||||
attribute.getName(), attribute.getDeclaringClass().getName());
|
||||
throw new AnnotationConfigurationException(msg);
|
||||
}
|
||||
|
||||
return attributeName.trim();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
|
|||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -87,25 +88,30 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
|
|||
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
|
||||
|
||||
Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
|
||||
Map<String, String> attributeAliasMap = getAttributeAliasMap(annotationType);
|
||||
Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
|
||||
|
||||
for (Method attributeMethod : getAttributeMethods(annotationType)) {
|
||||
String attributeName = attributeMethod.getName();
|
||||
Object attributeValue = attributes.get(attributeName);
|
||||
|
||||
// if attribute not present, check alias
|
||||
// if attribute not present, check aliases
|
||||
if (attributeValue == null) {
|
||||
String aliasName = attributeAliasMap.get(attributeName);
|
||||
if (aliasName != null) {
|
||||
Object aliasValue = attributes.get(aliasName);
|
||||
if (aliasValue != null) {
|
||||
attributeValue = aliasValue;
|
||||
attributes.put(attributeName, attributeValue);
|
||||
List<String> aliasNames = attributeAliasMap.get(attributeName);
|
||||
if (aliasNames != null) {
|
||||
for (String aliasName : aliasNames) {
|
||||
if (aliasName != null) {
|
||||
Object aliasValue = attributes.get(aliasName);
|
||||
if (aliasValue != null) {
|
||||
attributeValue = aliasValue;
|
||||
attributes.put(attributeName, attributeValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if alias not present, check default
|
||||
// if aliases not present, check default
|
||||
if (attributeValue == null) {
|
||||
Object defaultValue = getDefaultValue(annotationType, attributeName);
|
||||
if (defaultValue != null) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue