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.annotation.Annotation;
|
||||||
import java.lang.reflect.AnnotatedElement;
|
import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -44,7 +45,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
||||||
|
|
||||||
private final S source;
|
private final S source;
|
||||||
|
|
||||||
private final Map<String, String> attributeAliasMap;
|
private final Map<String, List<String>> attributeAliasMap;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -83,13 +84,15 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Object getAttributeValue(Method attributeMethod) {
|
public final Object getAttributeValue(Method attributeMethod) {
|
||||||
String attributeName = attributeMethod.getName();
|
final String attributeName = attributeMethod.getName();
|
||||||
Object attributeValue = getRawAttributeValue(attributeMethod);
|
Object attributeValue = getRawAttributeValue(attributeMethod);
|
||||||
|
|
||||||
String aliasName = this.attributeAliasMap.get(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) {
|
if (aliasName != null) {
|
||||||
Object aliasValue = getRawAttributeValue(aliasName);
|
Object aliasValue = getRawAttributeValue(aliasName);
|
||||||
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
|
|
||||||
|
|
||||||
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
|
||||||
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
|
||||||
|
|
@ -103,11 +106,13 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the user didn't declare the annotation with an explicit value,
|
// If the user didn't declare the annotation with an explicit value,
|
||||||
// return the value of the alias.
|
// use the value of the alias instead.
|
||||||
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
|
||||||
attributeValue = aliasValue;
|
attributeValue = aliasValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return attributeValue;
|
return attributeValue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ import java.lang.annotation.Target;
|
||||||
*
|
*
|
||||||
* <h3>Usage Scenarios</h3>
|
* <h3>Usage Scenarios</h3>
|
||||||
* <ul>
|
* <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
|
* annotation, {@code @AliasFor} can be declared on a pair of attributes to
|
||||||
* signal that they are interchangeable aliases for each other.</li>
|
* 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
|
* {@link #annotation} attribute of {@code @AliasFor} is set to a different
|
||||||
* annotation than the one that declares it, the {@link #attribute} is
|
* 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
|
* 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
|
* control over exactly which attributes are overridden within an annotation
|
||||||
* hierarchy. In fact, with {@code @AliasFor} it is even possible to declare
|
* hierarchy. In fact, with {@code @AliasFor} it is even possible to declare
|
||||||
* an alias for the {@code value} attribute of a meta-annotation.</li>
|
* 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>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h3>Usage Requirements</h3>
|
* <h3>Usage Requirements</h3>
|
||||||
|
|
@ -57,31 +62,44 @@ import java.lang.annotation.Target;
|
||||||
*
|
*
|
||||||
* <h3>Implementation Requirements</h3>
|
* <h3>Implementation Requirements</h3>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><strong>Aliases within an annotation</strong>:
|
* <li><strong>Explicit aliases within an annotation</strong>:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>Each attribute that makes up an aliased pair must be annotated with
|
* <li>Each attribute that makes up an aliased pair must be annotated with
|
||||||
* {@code @AliasFor}, and either the {@link #attribute} or the {@link #value}
|
* {@code @AliasFor}, and either {@link #attribute} or {@link #value} must
|
||||||
* attribute must reference the <em>other</em> attribute in the pair.</li>
|
* 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 the same return type.</li>
|
||||||
* <li>Aliased attributes must declare a default value.</li>
|
* <li>Aliased attributes must declare a default value.</li>
|
||||||
* <li>Aliased attributes must declare the same 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>
|
* </ol>
|
||||||
* </li>
|
* </li>
|
||||||
* <li><strong>Alias for attribute in meta-annotation</strong>:
|
* <li><strong>Explicit alias for attribute in meta-annotation</strong>:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>The attribute that is an alias for an attribute in a meta-annotation
|
* <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
|
* must be annotated with {@code @AliasFor}, and {@link #attribute} must
|
||||||
* reference the aliased attribute in the meta-annotation.</li>
|
* reference the attribute in the meta-annotation.</li>
|
||||||
* <li>Aliased attributes must declare the same return type.</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
|
* <li>The referenced meta-annotation must be <em>meta-present</em> on the
|
||||||
* annotation class that declares {@code @AliasFor}.</li>
|
* annotation class that declares {@code @AliasFor}.</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
* </li>
|
* </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h3>Example: Aliases within an Annotation</h3>
|
* <h3>Example: Explicit Aliases within an Annotation</h3>
|
||||||
* <pre class="code"> public @interface ContextConfiguration {
|
* <pre class="code"> public @interface ContextConfiguration {
|
||||||
*
|
*
|
||||||
* @AliasFor("locations")
|
* @AliasFor("locations")
|
||||||
|
|
@ -93,7 +111,7 @@ import java.lang.annotation.Target;
|
||||||
* // ...
|
* // ...
|
||||||
* }</pre>
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <h3>Example: Alias for Attribute in Meta-annotation</h3>
|
* <h3>Example: Explicit Alias for Attribute in Meta-annotation</h3>
|
||||||
* <pre class="code"> @ContextConfiguration
|
* <pre class="code"> @ContextConfiguration
|
||||||
* public @interface MyTestConfig {
|
* public @interface MyTestConfig {
|
||||||
*
|
*
|
||||||
|
|
@ -101,6 +119,20 @@ import java.lang.annotation.Target;
|
||||||
* String[] xmlFiles();
|
* String[] xmlFiles();
|
||||||
* }</pre>
|
* }</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>
|
* <h3>Spring Annotations Supporting Attribute Aliases</h3>
|
||||||
* <p>As of Spring Framework 4.2, several annotations within core Spring
|
* <p>As of Spring Framework 4.2, several annotations within core Spring
|
||||||
* have been updated to use {@code @AliasFor} to configure their internal
|
* 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.Assert;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General utility methods for finding annotations and meta-annotations on
|
* General utility methods for finding annotations and meta-annotations on
|
||||||
|
|
@ -957,13 +956,21 @@ public class AnnotatedElementUtils {
|
||||||
|
|
||||||
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
|
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
|
||||||
String attributeName = attributeMethod.getName();
|
String attributeName = attributeMethod.getName();
|
||||||
String aliasedAttributeName = AnnotationUtils.getAliasedAttributeName(attributeMethod,
|
List<String> aliases = AnnotationUtils.getAliasedAttributeNames(attributeMethod, targetAnnotationType);
|
||||||
targetAnnotationType);
|
|
||||||
|
|
||||||
// Explicit annotation attribute override declared via @AliasFor
|
// Explicit annotation attribute override declared via @AliasFor
|
||||||
if (StringUtils.hasText(aliasedAttributeName) && attributes.containsKey(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);
|
overrideAttribute(element, annotation, attributes, attributeName, aliasedAttributeName);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Implicit annotation attribute override based on convention
|
// Implicit annotation attribute override based on convention
|
||||||
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
|
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
|
||||||
overrideAttribute(element, annotation, attributes, attributeName, attributeName);
|
overrideAttribute(element, annotation, attributes, attributeName, attributeName);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
@ -422,12 +423,15 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
Assert.notNull(expectedType, "expectedType must not be null");
|
Assert.notNull(expectedType, "expectedType must not be null");
|
||||||
|
|
||||||
T attributeValue = getAttribute(attributeName, expectedType);
|
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) {
|
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 elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
|
||||||
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
|
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.",
|
"are present with values of [%s] and [%s], but only one is permitted.",
|
||||||
|
|
@ -436,11 +440,20 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
|
||||||
throw new AnnotationConfigurationException(msg);
|
throw new AnnotationConfigurationException(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!attributeDeclared) {
|
// 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;
|
attributeValue = aliasValue;
|
||||||
}
|
}
|
||||||
|
// Else: if we're not expecting an array, we can rely on the behavior of
|
||||||
assertAttributePresence(attributeName, aliasName, attributeValue);
|
// ObjectUtils.isEmpty().
|
||||||
|
else if (attributeEmpty && !aliasEmpty) {
|
||||||
|
attributeValue = aliasValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertAttributePresence(attributeName, aliasNames, attributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
return 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) {
|
if (attributeValue == null) {
|
||||||
throw new IllegalArgumentException(String.format(
|
throw new IllegalArgumentException(String.format(
|
||||||
"Neither attribute '%s' nor its alias '%s' was found in attributes for annotation [%s]",
|
"Neither attribute '%s' nor one of its aliases %s was found in attributes for annotation [%s]",
|
||||||
attributeName, aliasName, this.displayName));
|
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
|
* <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
|
* 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>
|
* <h3>Meta-annotation Support</h3>
|
||||||
* <p>Most {@code find*()} methods and some {@code get*()} methods in this
|
* <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 =
|
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
|
||||||
new ConcurrentReferenceHashMap<Class<?>, Boolean>(256);
|
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 =
|
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
|
||||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Boolean>(256);
|
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Boolean>(256);
|
||||||
|
|
||||||
private static final Map<Class<? extends Annotation>, Map<String, String>> attributeAliasesCache =
|
private static final Map<Class<? extends Annotation>, Map<String, List<String>>> attributeAliasesCache =
|
||||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, String>>(256);
|
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, List<String>>>(256);
|
||||||
|
|
||||||
private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache =
|
private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache =
|
||||||
new ConcurrentReferenceHashMap<Class<? extends Annotation>, List<Method>>(256);
|
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
|
* @param annotationType the type of annotation to look for
|
||||||
* @return the first matching annotation, or {@code null} if not found
|
* @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) {
|
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);
|
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
|
||||||
A result = (A) findAnnotationCache.get(cacheKey);
|
A result = (A) findAnnotationCache.get(cacheKey);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
|
|
@ -653,7 +672,7 @@ public abstract class AnnotationUtils {
|
||||||
findAnnotationCache.put(cacheKey, result);
|
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));
|
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
|
* Determine if the supplied {@link Annotation} is defined in the core JDK
|
||||||
* {@code java.lang.annotation} package.
|
* {@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.
|
* in the supplied annotation type.
|
||||||
* <p>The map is keyed by attribute name with each value representing
|
* <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
|
* a list of names of aliased attributes.
|
||||||
* the map there will be a corresponding {@code [y, x]} entry in the map.
|
* <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
|
* <p>An empty return value implies that the annotation does not declare
|
||||||
* any attribute aliases.
|
* any attribute aliases.
|
||||||
* @param annotationType the annotation type to find attribute aliases in
|
* @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
|
* @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) {
|
if (annotationType == null) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> map = attributeAliasesCache.get(annotationType);
|
Map<String, List<String>> map = attributeAliasesCache.get(annotationType);
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
map = new HashMap<String, String>();
|
map = new HashMap<String, List<String>>();
|
||||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||||
String attributeName = attribute.getName();
|
List<String> aliasNames = getAliasedAttributeNames(attribute);
|
||||||
String aliasedAttributeName = getAliasedAttributeName(attribute);
|
if (!aliasNames.isEmpty()) {
|
||||||
if (aliasedAttributeName != null) {
|
map.put(attribute.getName(), aliasNames);
|
||||||
map.put(attributeName, aliasedAttributeName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1420,7 +1469,7 @@ public abstract class AnnotationUtils {
|
||||||
|
|
||||||
synthesizable = Boolean.FALSE;
|
synthesizable = Boolean.FALSE;
|
||||||
for (Method attribute : getAttributeMethods(annotationType)) {
|
for (Method attribute : getAttributeMethods(annotationType)) {
|
||||||
if (getAliasedAttributeName(attribute) != null) {
|
if (!getAliasedAttributeNames(attribute).isEmpty()) {
|
||||||
synthesizable = Boolean.TRUE;
|
synthesizable = Boolean.TRUE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1446,184 +1495,85 @@ public abstract class AnnotationUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the aliased attribute configured via
|
* Get the names of the aliased attributes configured via
|
||||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
|
||||||
* <p>This method does not resolve aliases in other annotations. In
|
* <p>This method does not resolve meta-annotation attribute overrides.
|
||||||
* other words, if {@code @AliasFor} is present on the supplied
|
* @param attribute the attribute to find aliases for; never {@code null}
|
||||||
* {@code attribute} but {@linkplain AliasFor#annotation references an
|
* @return the names of the aliased attributes; never {@code null}, though
|
||||||
* annotation} other than {@link Annotation}, this method will return
|
* potentially <em>empty</em>
|
||||||
* {@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
|
|
||||||
* @throws IllegalArgumentException if the supplied attribute method is
|
* @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
|
* @throws AnnotationConfigurationException if invalid configuration of
|
||||||
* {@code @AliasFor} is detected
|
* {@code @AliasFor} is detected
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
* @see #getAliasedAttributeName(Method, Class)
|
* @see #getAliasedAttributeNames(Method, Class)
|
||||||
*/
|
*/
|
||||||
static String getAliasedAttributeName(Method attribute) {
|
static List<String> getAliasedAttributeNames(Method attribute) {
|
||||||
return getAliasedAttributeName(attribute, (Class<? extends Annotation>) null);
|
return getAliasedAttributeNames(attribute, (Class<? extends Annotation>) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the aliased attribute configured via
|
* Get the names of the aliased attributes configured via
|
||||||
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
|
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
|
||||||
* @param attribute the attribute to find an alias for
|
* <p>If the supplied {@code metaAnnotationType} is non-null, the
|
||||||
* @param targetAnnotationType the type of annotation in which 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
|
* aliased attribute is allowed to be declared; {@code null} implies
|
||||||
* <em>within the same annotation</em>
|
* <em>within the same annotation</em> as the supplied attribute
|
||||||
* @return the name of the aliased attribute, or {@code null} if not found
|
* @return the names of the aliased attributes; never {@code null}, though
|
||||||
|
* potentially <em>empty</em>
|
||||||
* @throws IllegalArgumentException if the supplied attribute method is
|
* @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
|
* @throws AnnotationConfigurationException if invalid configuration of
|
||||||
* {@code @AliasFor} is detected
|
* {@code @AliasFor} is detected
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
static List<String> getAliasedAttributeNames(Method attribute, Class<? extends Annotation> metaAnnotationType) {
|
||||||
static String getAliasedAttributeName(Method attribute, Class<? extends Annotation> targetAnnotationType) {
|
Assert.notNull(attribute, "attribute method must not be null");
|
||||||
Class<?> declaringClass = attribute.getDeclaringClass();
|
Assert.isTrue(!Annotation.class.equals(metaAnnotationType),
|
||||||
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
|
"metaAnnotationType must not be java.lang.annotation.Annotation");
|
||||||
Assert.isTrue(!Annotation.class.equals(targetAnnotationType),
|
|
||||||
"targetAnnotationType must not be java.lang.annotation.Annotation");
|
|
||||||
|
|
||||||
String attributeName = attribute.getName();
|
AliasDescriptor descriptor = AliasDescriptor.from(attribute);
|
||||||
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
|
|
||||||
|
|
||||||
// Nothing to check
|
// No alias declared via @AliasFor?
|
||||||
if (aliasFor == null) {
|
if (descriptor == null) {
|
||||||
return null;
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends Annotation> sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
|
// Searching for explicit meta-annotation attribute override?
|
||||||
Class<? extends Annotation> aliasedAnnotationType = aliasFor.annotation();
|
if (metaAnnotationType != null) {
|
||||||
|
if (descriptor.isAliasFor(metaAnnotationType)) {
|
||||||
boolean searchWithinSameAnnotation = (targetAnnotationType == null);
|
return Collections.singletonList(descriptor.aliasedAttributeName);
|
||||||
boolean sameTargetDeclared =
|
}
|
||||||
(sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType));
|
// Else: explicit attribute override for a different meta-annotation
|
||||||
|
return Collections.emptyList();
|
||||||
// Explicit alias for a different target meta-annotation?
|
|
||||||
if (!searchWithinSameAnnotation && !targetAnnotationType.equals(aliasedAnnotationType)) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String aliasedAttributeName = getAliasedAttributeName(aliasFor, attribute);
|
// Explicit alias pair?
|
||||||
|
if (descriptor.isAliasPair) {
|
||||||
if (!StringUtils.hasText(aliasedAttributeName)) {
|
return Collections.singletonList(descriptor.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) {
|
// Else: search for implicit aliases
|
||||||
// Target annotation is not meta-present?
|
List<String> aliases = new ArrayList<String>();
|
||||||
if (findAnnotation(sourceAnnotationType, aliasedAnnotationType) == null) {
|
for (Method currentAttribute : getAttributeMethods(descriptor.sourceAnnotationType)) {
|
||||||
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.",
|
// An attribute cannot alias itself
|
||||||
attributeName, sourceAnnotationType.getName(), aliasedAttributeName,
|
if (attribute.equals(currentAttribute)) {
|
||||||
aliasedAnnotationType.getName());
|
continue;
|
||||||
throw new AnnotationConfigurationException(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
aliasedAnnotationType = sourceAnnotationType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrong search scope?
|
// If two attributes override the same attribute in the same meta-annotation,
|
||||||
if (searchWithinSameAnnotation && !sameTargetDeclared) {
|
// they are "implicit" aliases for each other.
|
||||||
return null;
|
AliasDescriptor otherDescriptor = AliasDescriptor.from(currentAttribute);
|
||||||
}
|
if (descriptor.equals(otherDescriptor)) {
|
||||||
|
descriptor.validateAgainst(otherDescriptor);
|
||||||
Method aliasedAttribute;
|
aliases.add(otherDescriptor.sourceAttributeName);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return aliases;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1677,6 +1627,7 @@ public abstract class AnnotationUtils {
|
||||||
* Determine if the supplied {@code method} is an annotation attribute method.
|
* Determine if the supplied {@code method} is an annotation attribute method.
|
||||||
* @param method the method to check
|
* @param method the method to check
|
||||||
* @return {@code true} if the method is an attribute method
|
* @return {@code true} if the method is an attribute method
|
||||||
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
static boolean isAttributeMethod(Method method) {
|
static boolean isAttributeMethod(Method method) {
|
||||||
return (method != null && method.getParameterTypes().length == 0 && method.getReturnType() != void.class);
|
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.
|
* Determine if the supplied method is an "annotationType" method.
|
||||||
* @return {@code true} if the method is an "annotationType" method
|
* @return {@code true} if the method is an "annotationType" method
|
||||||
* @see Annotation#annotationType()
|
* @see Annotation#annotationType()
|
||||||
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
static boolean isAnnotationTypeMethod(Method method) {
|
static boolean isAnnotationTypeMethod(Method method) {
|
||||||
return (method != null && method.getName().equals("annotationType") && method.getParameterTypes().length == 0);
|
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();
|
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
|
// Validate @AliasFor configuration
|
||||||
Map<String, String> aliasMap = getAttributeAliasMap(annotationType);
|
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
|
||||||
Set<String> validated = new HashSet<String>();
|
|
||||||
for (String attributeName : aliasMap.keySet()) {
|
for (String attributeName : aliasMap.keySet()) {
|
||||||
String aliasedAttributeName = aliasMap.get(attributeName);
|
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||||
|
continue;
|
||||||
if (validated.add(attributeName) && validated.add(aliasedAttributeName)) {
|
}
|
||||||
Object value = attributes.get(attributeName);
|
Object value = attributes.get(attributeName);
|
||||||
Object aliasedValue = attributes.get(aliasedAttributeName);
|
boolean valuePresent = (value != null && value != DEFAULT_VALUE_PLACEHOLDER);
|
||||||
|
|
||||||
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && (value != DEFAULT_VALUE_PLACEHOLDER)
|
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
|
||||||
&& (aliasedValue != DEFAULT_VALUE_PLACEHOLDER)) {
|
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object aliasedValue = attributes.get(aliasedAttributeName);
|
||||||
|
boolean aliasPresent = (aliasedValue != null && aliasedValue != DEFAULT_VALUE_PLACEHOLDER);
|
||||||
|
|
||||||
|
// 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 elementAsString = (element == null ? "unknown element" : element.toString());
|
||||||
String msg = String.format(
|
String msg = String.format("In AnnotationAttributes for annotation [%s] declared on [%s], "
|
||||||
"In AnnotationAttributes for annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
|
+ "attribute [%s] and its alias [%s] are declared with values of [%s] and [%s], "
|
||||||
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
|
+ "but only one declaration is permitted.", annotationType.getName(),
|
||||||
annotationType.getName(), elementAsString, attributeName, aliasedAttributeName,
|
elementAsString, attributeName, aliasedAttributeName,
|
||||||
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
|
||||||
throw new AnnotationConfigurationException(msg);
|
throw new AnnotationConfigurationException(msg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Replace default values with aliased values...
|
else if (aliasPresent) {
|
||||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
// Replace value with aliasedValue
|
||||||
attributes.put(attributeName,
|
attributes.put(attributeName,
|
||||||
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
valuesAlreadyReplaced.add(attributeName);
|
||||||
}
|
}
|
||||||
if (aliasedValue == DEFAULT_VALUE_PLACEHOLDER) {
|
else {
|
||||||
|
// Replace aliasedValue with value
|
||||||
attributes.put(aliasedAttributeName,
|
attributes.put(aliasedAttributeName,
|
||||||
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
|
||||||
|
valuesAlreadyReplaced.add(aliasedAttributeName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace any remaining placeholders with actual default values
|
||||||
for (String attributeName : attributes.keySet()) {
|
for (String attributeName : attributes.keySet()) {
|
||||||
|
if (valuesAlreadyReplaced.contains(attributeName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
Object value = attributes.get(attributeName);
|
Object value = attributes.get(attributeName);
|
||||||
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
if (value == DEFAULT_VALUE_PLACEHOLDER) {
|
||||||
attributes.put(attributeName,
|
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.AnnotatedElement;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
|
@ -87,25 +88,30 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
|
||||||
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
|
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
|
||||||
|
|
||||||
Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
|
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)) {
|
for (Method attributeMethod : getAttributeMethods(annotationType)) {
|
||||||
String attributeName = attributeMethod.getName();
|
String attributeName = attributeMethod.getName();
|
||||||
Object attributeValue = attributes.get(attributeName);
|
Object attributeValue = attributes.get(attributeName);
|
||||||
|
|
||||||
// if attribute not present, check alias
|
// if attribute not present, check aliases
|
||||||
if (attributeValue == null) {
|
if (attributeValue == null) {
|
||||||
String aliasName = attributeAliasMap.get(attributeName);
|
List<String> aliasNames = attributeAliasMap.get(attributeName);
|
||||||
|
if (aliasNames != null) {
|
||||||
|
for (String aliasName : aliasNames) {
|
||||||
if (aliasName != null) {
|
if (aliasName != null) {
|
||||||
Object aliasValue = attributes.get(aliasName);
|
Object aliasValue = attributes.get(aliasName);
|
||||||
if (aliasValue != null) {
|
if (aliasValue != null) {
|
||||||
attributeValue = aliasValue;
|
attributeValue = aliasValue;
|
||||||
attributes.put(attributeName, attributeValue);
|
attributes.put(attributeName, attributeValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if alias not present, check default
|
// if aliases not present, check default
|
||||||
if (attributeValue == null) {
|
if (attributeValue == null) {
|
||||||
Object defaultValue = getDefaultValue(annotationType, attributeName);
|
Object defaultValue = getDefaultValue(annotationType, attributeName);
|
||||||
if (defaultValue != null) {
|
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
|
@Test
|
||||||
public void getMergeAndSynthesizeAnnotationWithAliasedValueComposedAnnotation() {
|
public void getMergedAnnotationAttributesWithImplicitAliasesInMetaAnnotationOnComposedAnnotation() {
|
||||||
Class<?> element = AliasedValueComposedContextConfigClass.class;
|
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);
|
ContextConfig contextConfig = getMergedAnnotation(element, ContextConfig.class);
|
||||||
|
|
||||||
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), contextConfig);
|
assertNotNull("Should find @ContextConfig on " + element.getSimpleName(), contextConfig);
|
||||||
assertArrayEquals("locations", new String[] { "test.xml" }, contextConfig.locations());
|
assertArrayEquals("locations", new String[] { expected }, contextConfig.locations());
|
||||||
assertArrayEquals("value", new String[] { "test.xml" }, contextConfig.value());
|
assertArrayEquals("value", new String[] { expected }, contextConfig.value());
|
||||||
|
assertArrayEquals("classes", new Class<?>[0], contextConfig.classes());
|
||||||
|
|
||||||
// Verify contracts between utility methods:
|
// 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
|
@Test
|
||||||
|
|
@ -517,11 +563,11 @@ public class AnnotatedElementUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
|
public void findMergedAnnotationAttributesOnClassWithAttributeAliasInComposedAnnotationAndNestedAnnotationsInTargetAnnotation() {
|
||||||
|
String[] expected = new String[] { "com.example.app.test" };
|
||||||
Class<?> element = TestComponentScanClass.class;
|
Class<?> element = TestComponentScanClass.class;
|
||||||
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
|
AnnotationAttributes attributes = findMergedAnnotationAttributes(element, ComponentScan.class);
|
||||||
assertNotNull("Should find @ComponentScan on " + element, attributes);
|
assertNotNull("Should find @ComponentScan on " + element, attributes);
|
||||||
assertArrayEquals("basePackages for " + element, new String[] { "com.example.app.test" },
|
assertArrayEquals("basePackages for " + element, expected, attributes.getStringArray("basePackages"));
|
||||||
attributes.getStringArray("basePackages"));
|
|
||||||
|
|
||||||
Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
|
Filter[] excludeFilters = attributes.getAnnotationArray("excludeFilters", Filter.class);
|
||||||
assertNotNull(excludeFilters);
|
assertNotNull(excludeFilters);
|
||||||
|
|
@ -530,6 +576,22 @@ public class AnnotatedElementUtilsTests {
|
||||||
assertEquals(asList("*Test", "*Tests"), patterns);
|
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
|
@Test
|
||||||
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
|
public void findMergedAnnotationWithLocalAliasesThatConflictWithAttributesInMetaAnnotationByConvention() {
|
||||||
final String[] EMPTY = new String[] {};
|
final String[] EMPTY = new String[] {};
|
||||||
|
|
@ -716,6 +778,28 @@ public class AnnotatedElementUtilsTests {
|
||||||
String[] locations();
|
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
|
* Invalid because the configuration declares a value for 'value' and
|
||||||
* requires a value for the aliased 'locations'. So we likely end up with
|
* requires a value for the aliased 'locations'. So we likely end up with
|
||||||
|
|
@ -762,6 +846,10 @@ public class AnnotatedElementUtilsTests {
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@interface ComponentScan {
|
@interface ComponentScan {
|
||||||
|
|
||||||
|
@AliasFor("basePackages")
|
||||||
|
String[] value() default {};
|
||||||
|
|
||||||
|
@AliasFor("value")
|
||||||
String[] basePackages() default {};
|
String[] basePackages() default {};
|
||||||
|
|
||||||
Filter[] excludeFilters() default {};
|
Filter[] excludeFilters() default {};
|
||||||
|
|
@ -928,6 +1016,22 @@ public class AnnotatedElementUtilsTests {
|
||||||
static class AliasedValueComposedContextConfigClass {
|
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")
|
@InvalidAliasedComposedContextConfig(xmlConfigFiles = "test.xml")
|
||||||
static class InvalidAliasedComposedContextConfigClass {
|
static class InvalidAliasedComposedContextConfigClass {
|
||||||
}
|
}
|
||||||
|
|
@ -936,6 +1040,10 @@ public class AnnotatedElementUtilsTests {
|
||||||
static class AliasedComposedContextConfigAndTestPropSourceClass {
|
static class AliasedComposedContextConfigAndTestPropSourceClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ComponentScan(value = "com.example.app.test", basePackages = "com.example.app.test")
|
||||||
|
static class ComponentScanWithBasePackagesAndValueAliasClass {
|
||||||
|
}
|
||||||
|
|
||||||
@TestComponentScan(packages = "com.example.app.test")
|
@TestComponentScan(packages = "com.example.app.test")
|
||||||
static class TestComponentScanClass {
|
static class TestComponentScanClass {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,16 @@ package org.springframework.core.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
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.hamcrest.CoreMatchers.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
@ -36,7 +41,7 @@ import static org.junit.Assert.*;
|
||||||
*/
|
*/
|
||||||
public class AnnotationAttributesTests {
|
public class AnnotationAttributesTests {
|
||||||
|
|
||||||
private final AnnotationAttributes attributes = new AnnotationAttributes();
|
private AnnotationAttributes attributes = new AnnotationAttributes();
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final ExpectedException exception = ExpectedException.none();
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
@ -156,21 +161,56 @@ public class AnnotationAttributesTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAliasedString() {
|
public void getAliasedString() {
|
||||||
attributes.clear();
|
final String value = "metaverse";
|
||||||
attributes.put("name", "metaverse");
|
|
||||||
assertEquals("metaverse", getAliasedString("name"));
|
|
||||||
assertEquals("metaverse", getAliasedString("value"));
|
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("value", "metaverse");
|
attributes.put("name", value);
|
||||||
assertEquals("metaverse", getAliasedString("name"));
|
assertEquals(value, getAliasedString("name"));
|
||||||
assertEquals("metaverse", getAliasedString("value"));
|
assertEquals(value, getAliasedString("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("name", "metaverse");
|
attributes.put("value", value);
|
||||||
attributes.put("value", "metaverse");
|
assertEquals(value, getAliasedString("name"));
|
||||||
assertEquals("metaverse", getAliasedString("name"));
|
assertEquals(value, getAliasedString("value"));
|
||||||
assertEquals("metaverse", 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
|
@Test
|
||||||
|
|
@ -185,7 +225,7 @@ public class AnnotationAttributesTests {
|
||||||
@Test
|
@Test
|
||||||
public void getAliasedStringWithMissingAliasedAttributes() {
|
public void getAliasedStringWithMissingAliasedAttributes() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
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");
|
getAliasedString("name");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,71 +251,135 @@ public class AnnotationAttributesTests {
|
||||||
return attrs.getAliasedString(attributeName, Scope.class, null);
|
return attrs.getAliasedString(attributeName, Scope.class, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getAliasedStringWithImplicitAliases(String attributeName) {
|
||||||
|
return this.attributes.getAliasedString(attributeName, ImplicitAliasesContextConfig.class, null);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAliasedStringArray() {
|
public void getAliasedStringArray() {
|
||||||
final String[] INPUT = new String[] { "test.xml" };
|
final String[] INPUT = new String[] { "test.xml" };
|
||||||
final String[] EMPTY = new String[0];
|
final String[] EMPTY = new String[0];
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("locations", INPUT);
|
attributes.put("location", INPUT);
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("value", INPUT);
|
attributes.put("value", INPUT);
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("locations", INPUT);
|
attributes.put("location", INPUT);
|
||||||
attributes.put("value", INPUT);
|
attributes.put("value", INPUT);
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("locations", INPUT);
|
attributes.put("location", INPUT);
|
||||||
attributes.put("value", EMPTY);
|
attributes.put("value", EMPTY);
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("locations", EMPTY);
|
attributes.put("location", EMPTY);
|
||||||
attributes.put("value", INPUT);
|
attributes.put("value", INPUT);
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("locations"));
|
assertArrayEquals(INPUT, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
assertArrayEquals(INPUT, getAliasedStringArray("value"));
|
||||||
|
|
||||||
attributes.clear();
|
attributes.clear();
|
||||||
attributes.put("locations", EMPTY);
|
attributes.put("location", EMPTY);
|
||||||
attributes.put("value", EMPTY);
|
attributes.put("value", EMPTY);
|
||||||
assertArrayEquals(EMPTY, getAliasedStringArray("locations"));
|
assertArrayEquals(EMPTY, getAliasedStringArray("location"));
|
||||||
assertArrayEquals(EMPTY, getAliasedStringArray("value"));
|
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
|
@Test
|
||||||
public void getAliasedStringArrayWithMissingAliasedAttributes() {
|
public void getAliasedStringArrayWithMissingAliasedAttributes() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
exception.expect(IllegalArgumentException.class);
|
||||||
exception.expectMessage(equalTo("Neither attribute 'locations' nor its alias 'value' was found in attributes for annotation [unknown]"));
|
exception.expectMessage(equalTo("Neither attribute 'location' nor one of its aliases [value] was found in attributes for annotation [unknown]"));
|
||||||
getAliasedStringArray("locations");
|
getAliasedStringArray("location");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAliasedStringArrayWithDifferentAliasedValues() {
|
public void getAliasedStringArrayWithDifferentAliasedValues() {
|
||||||
attributes.put("locations", new String[] { "1.xml" });
|
attributes.put("location", new String[] { "1.xml" });
|
||||||
attributes.put("value", new String[] { "2.xml" });
|
attributes.put("value", new String[] { "2.xml" });
|
||||||
|
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(containsString("In annotation [" + ContextConfig.class.getName() + "]"));
|
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("[{1.xml}] and [{2.xml}]"));
|
||||||
exception.expectMessage(containsString("but only one is permitted"));
|
exception.expectMessage(containsString("but only one is permitted"));
|
||||||
|
|
||||||
getAliasedStringArray("locations");
|
getAliasedStringArray("location");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getAliasedStringArray(String attributeName) {
|
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);
|
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
|
@Test
|
||||||
public void getAliasedClassArray() {
|
public void getAliasedClassArray() {
|
||||||
final Class<?>[] INPUT = new Class<?>[] { String.class };
|
final Class<?>[] INPUT = new Class<?>[] { String.class };
|
||||||
|
|
@ -316,10 +420,46 @@ public class AnnotationAttributesTests {
|
||||||
assertArrayEquals(EMPTY, getAliasedClassArray("value"));
|
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
|
@Test
|
||||||
public void getAliasedClassArrayWithMissingAliasedAttributes() {
|
public void getAliasedClassArrayWithMissingAliasedAttributes() {
|
||||||
exception.expect(IllegalArgumentException.class);
|
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");
|
getAliasedClassArray("classes");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,6 +481,14 @@ public class AnnotationAttributesTests {
|
||||||
return attributes.getAliasedClassArray(attributeName, Filter.class, null);
|
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 {
|
enum Color {
|
||||||
RED, WHITE, BLUE
|
RED, WHITE, BLUE
|
||||||
|
|
@ -362,19 +510,6 @@ public class AnnotationAttributesTests {
|
||||||
static class FilteredClass {
|
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}.
|
* 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.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
@ -38,6 +38,7 @@ import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
import static java.util.Arrays.*;
|
import static java.util.Arrays.*;
|
||||||
import static java.util.stream.Collectors.*;
|
import static java.util.stream.Collectors.*;
|
||||||
|
|
@ -56,10 +57,31 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
*/
|
*/
|
||||||
public class AnnotationUtilsTests {
|
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
|
@Rule
|
||||||
public final ExpectedException exception = ExpectedException.none();
|
public final ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void clearCachesBeforeTests() {
|
||||||
|
clearCaches();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findMethodAnnotationOnLeaf() throws Exception {
|
public void findMethodAnnotationOnLeaf() throws Exception {
|
||||||
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
Method m = Leaf.class.getMethod("annotatedOnLeaf");
|
||||||
|
|
@ -308,7 +330,7 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
|
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
|
||||||
// no class-level annotation
|
// 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, NonAnnotatedInterface.class));
|
||||||
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
|
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
|
||||||
|
|
||||||
|
|
@ -323,7 +345,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
// non-inherited class-level annotation; note: @Order is not inherited,
|
// non-inherited class-level annotation; note: @Order is not inherited,
|
||||||
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
|
// 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,
|
assertEquals(NonInheritedAnnotationInterface.class,
|
||||||
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
|
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
|
||||||
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
|
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
|
||||||
|
|
@ -335,7 +357,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void findAnnotationDeclaringClassForTypesWithMultipleCandidateTypes() {
|
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
|
// no class-level annotation
|
||||||
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
|
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
|
||||||
|
|
@ -461,7 +483,7 @@ public class AnnotationUtilsTests {
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
|
exception.expectMessage(containsString("attribute 'value' and its alias 'path'"));
|
||||||
exception.expectMessage(containsString("values of [/enigma] and [/test]"));
|
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);
|
getAnnotationAttributes(webMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -524,21 +546,21 @@ public class AnnotationUtilsTests {
|
||||||
Set<MyRepeatable> annotations = getRepeatableAnnotations(method, MyRepeatable.class, MyRepeatableContainer.class);
|
Set<MyRepeatable> annotations = getRepeatableAnnotations(method, MyRepeatable.class, MyRepeatableContainer.class);
|
||||||
assertNotNull(annotations);
|
assertNotNull(annotations);
|
||||||
List<String> values = annotations.stream().map(MyRepeatable::value).collect(toList());
|
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
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
|
public void getRepeatableAnnotationsDeclaredOnClassWithMissingAttributeAliasDeclaration() throws Exception {
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
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(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);
|
getRepeatableAnnotations(BrokenConfigHierarchyTestCase.class, BrokenContextConfig.class, BrokenHierarchy.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnClassWithAttributeAliases() throws Exception {
|
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);
|
Set<ContextConfig> annotations = getRepeatableAnnotations(ConfigHierarchyTestCase.class, ContextConfig.class, null);
|
||||||
assertNotNull(annotations);
|
assertNotNull(annotations);
|
||||||
|
|
@ -556,8 +578,8 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnClass() {
|
public void getRepeatableAnnotationsDeclaredOnClass() {
|
||||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||||
|
|
||||||
// Java 8
|
// Java 8
|
||||||
MyRepeatable[] array = MyRepeatableClass.class.getAnnotationsByType(MyRepeatable.class);
|
MyRepeatable[] array = MyRepeatableClass.class.getAnnotationsByType(MyRepeatable.class);
|
||||||
|
|
@ -581,8 +603,8 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnSuperclass() {
|
public void getRepeatableAnnotationsDeclaredOnSuperclass() {
|
||||||
final Class<?> clazz = SubMyRepeatableClass.class;
|
final Class<?> clazz = SubMyRepeatableClass.class;
|
||||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||||
|
|
||||||
// Java 8
|
// Java 8
|
||||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||||
|
|
@ -606,8 +628,8 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnClassAndSuperclass() {
|
public void getRepeatableAnnotationsDeclaredOnClassAndSuperclass() {
|
||||||
final Class<?> clazz = SubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
final Class<?> clazz = SubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||||
final List<String> expectedValuesJava = Arrays.asList("X", "Y", "Z");
|
final List<String> expectedValuesJava = asList("X", "Y", "Z");
|
||||||
final List<String> expectedValuesSpring = Arrays.asList("X", "Y", "Z", "meta2");
|
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
|
||||||
|
|
||||||
// Java 8
|
// Java 8
|
||||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||||
|
|
@ -631,8 +653,8 @@ public class AnnotationUtilsTests {
|
||||||
@Test
|
@Test
|
||||||
public void getRepeatableAnnotationsDeclaredOnMultipleSuperclasses() {
|
public void getRepeatableAnnotationsDeclaredOnMultipleSuperclasses() {
|
||||||
final Class<?> clazz = SubSubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
final Class<?> clazz = SubSubMyRepeatableWithAdditionalLocalDeclarationsClass.class;
|
||||||
final List<String> expectedValuesJava = Arrays.asList("X", "Y", "Z");
|
final List<String> expectedValuesJava = asList("X", "Y", "Z");
|
||||||
final List<String> expectedValuesSpring = Arrays.asList("X", "Y", "Z", "meta2");
|
final List<String> expectedValuesSpring = asList("X", "Y", "Z", "meta2");
|
||||||
|
|
||||||
// Java 8
|
// Java 8
|
||||||
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
MyRepeatable[] array = clazz.getAnnotationsByType(MyRepeatable.class);
|
||||||
|
|
@ -655,8 +677,8 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDeclaredRepeatableAnnotationsDeclaredOnClass() {
|
public void getDeclaredRepeatableAnnotationsDeclaredOnClass() {
|
||||||
final List<String> expectedValuesJava = Arrays.asList("A", "B", "C");
|
final List<String> expectedValuesJava = asList("A", "B", "C");
|
||||||
final List<String> expectedValuesSpring = Arrays.asList("A", "B", "C", "meta1");
|
final List<String> expectedValuesSpring = asList("A", "B", "C", "meta1");
|
||||||
|
|
||||||
// Java 8
|
// Java 8
|
||||||
MyRepeatable[] array = MyRepeatableClass.class.getDeclaredAnnotationsByType(MyRepeatable.class);
|
MyRepeatable[] array = MyRepeatableClass.class.getDeclaredAnnotationsByType(MyRepeatable.class);
|
||||||
|
|
@ -698,16 +720,45 @@ public class AnnotationUtilsTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getAliasedAttributeNameFromWrongTargetAnnotation() throws Exception {
|
public void getAliasedAttributeNamesFromWrongTargetAnnotation() throws Exception {
|
||||||
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
Method attribute = AliasedComposedContextConfig.class.getDeclaredMethod("xmlConfigFile");
|
||||||
assertNull("xmlConfigFile is not an alias for @Component.",
|
assertThat("xmlConfigFile is not an alias for @Component.",
|
||||||
getAliasedAttributeName(attribute, Component.class));
|
getAliasedAttributeNames(attribute, Component.class), is(empty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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");
|
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
|
@Test
|
||||||
|
|
@ -746,9 +797,9 @@ public class AnnotationUtilsTests {
|
||||||
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
|
public void synthesizeAnnotationWhereAliasForIsMissingAttributeDeclaration() throws Exception {
|
||||||
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
|
AliasForWithMissingAttributeDeclaration annotation = AliasForWithMissingAttributeDeclarationClass.class.getAnnotation(AliasForWithMissingAttributeDeclaration.class);
|
||||||
exception.expect(AnnotationConfigurationException.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(AliasForWithMissingAttributeDeclaration.class.getName()));
|
||||||
exception.expectMessage(containsString("is missing required 'attribute' value"));
|
exception.expectMessage(endsWith("is missing required 'attribute' value."));
|
||||||
synthesizeAnnotation(annotation);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -756,10 +807,10 @@ public class AnnotationUtilsTests {
|
||||||
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
|
public void synthesizeAnnotationWhereAliasForHasDuplicateAttributeDeclaration() throws Exception {
|
||||||
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
|
AliasForWithDuplicateAttributeDeclaration annotation = AliasForWithDuplicateAttributeDeclarationClass.class.getAnnotation(AliasForWithDuplicateAttributeDeclaration.class);
|
||||||
exception.expect(AnnotationConfigurationException.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(AliasForWithDuplicateAttributeDeclaration.class.getName()));
|
||||||
exception.expectMessage(containsString("attribute 'attribute' and its alias 'value' are present with values of [baz] and [bar]"));
|
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);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -767,7 +818,7 @@ public class AnnotationUtilsTests {
|
||||||
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
public void synthesizeAnnotationWithAttributeAliasForNonexistentAttribute() throws Exception {
|
||||||
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
AliasForNonexistentAttribute annotation = AliasForNonexistentAttributeClass.class.getAnnotation(AliasForNonexistentAttribute.class);
|
||||||
exception.expect(AnnotationConfigurationException.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(AliasForNonexistentAttribute.class.getName()));
|
||||||
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]"));
|
exception.expectMessage(containsString("is declared as an @AliasFor nonexistent attribute [bar]"));
|
||||||
synthesizeAnnotation(annotation);
|
synthesizeAnnotation(annotation);
|
||||||
|
|
@ -778,9 +829,9 @@ public class AnnotationUtilsTests {
|
||||||
AliasForWithoutMirroredAliasFor annotation =
|
AliasForWithoutMirroredAliasFor annotation =
|
||||||
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
|
AliasForWithoutMirroredAliasForClass.class.getAnnotation(AliasForWithoutMirroredAliasFor.class);
|
||||||
exception.expect(AnnotationConfigurationException.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(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);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -789,12 +840,8 @@ public class AnnotationUtilsTests {
|
||||||
AliasForWithMirroredAliasForWrongAttribute annotation =
|
AliasForWithMirroredAliasForWrongAttribute annotation =
|
||||||
AliasForWithMirroredAliasForWrongAttributeClass.class.getAnnotation(AliasForWithMirroredAliasForWrongAttribute.class);
|
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.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(containsString("Attribute [bar] in"));
|
exception.expectMessage(startsWith("Attribute [bar] in"));
|
||||||
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
|
exception.expectMessage(containsString(AliasForWithMirroredAliasForWrongAttribute.class.getName()));
|
||||||
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
|
exception.expectMessage(either(containsString("must be declared as an @AliasFor [foo], not [quux]")).
|
||||||
or(containsString("is declared as an @AliasFor nonexistent attribute [quux]")));
|
or(containsString("is declared as an @AliasFor nonexistent attribute [quux]")));
|
||||||
|
|
@ -808,13 +855,9 @@ public class AnnotationUtilsTests {
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
exception.expectMessage(containsString(AliasForAttributeOfDifferentType.class.getName()));
|
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 [foo]"));
|
||||||
exception.expectMessage(containsString("attribute [bar]"));
|
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);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -825,13 +868,9 @@ public class AnnotationUtilsTests {
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
|
exception.expectMessage(containsString(AliasForWithMissingDefaultValues.class.getName()));
|
||||||
|
exception.expectMessage(containsString("attribute [foo] in annotation"));
|
||||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
exception.expectMessage(containsString("attribute [bar] in annotation"));
|
||||||
// reflection, we cannot make the test dependent on any specific ordering.
|
exception.expectMessage(endsWith("must declare default values."));
|
||||||
// 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"));
|
|
||||||
synthesizeAnnotation(annotation);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -842,13 +881,9 @@ public class AnnotationUtilsTests {
|
||||||
exception.expect(AnnotationConfigurationException.class);
|
exception.expect(AnnotationConfigurationException.class);
|
||||||
exception.expectMessage(startsWith("Misconfigured aliases"));
|
exception.expectMessage(startsWith("Misconfigured aliases"));
|
||||||
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
|
exception.expectMessage(containsString(AliasForAttributeWithDifferentDefaultValue.class.getName()));
|
||||||
|
exception.expectMessage(containsString("attribute [foo] in annotation"));
|
||||||
// Since JDK 7+ does not guarantee consistent ordering of methods returned using
|
exception.expectMessage(containsString("attribute [bar] in annotation"));
|
||||||
// reflection, we cannot make the test dependent on any specific ordering.
|
exception.expectMessage(endsWith("must declare the same default value."));
|
||||||
// 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"));
|
|
||||||
synthesizeAnnotation(annotation);
|
synthesizeAnnotation(annotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -887,13 +922,91 @@ public class AnnotationUtilsTests {
|
||||||
assertEquals("actual value attribute: ", "/test", synthesizedWebMapping2.value());
|
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
|
@Test
|
||||||
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
|
public void synthesizeAnnotationFromMapWithoutAttributeAliases() throws Exception {
|
||||||
Component component = WebController.class.getAnnotation(Component.class);
|
Component component = WebController.class.getAnnotation(Component.class);
|
||||||
assertNotNull(component);
|
assertNotNull(component);
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = Collections.singletonMap(VALUE, "webController");
|
||||||
map.put(VALUE, "webController");
|
|
||||||
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
|
Component synthesizedComponent = synthesizeAnnotation(map, Component.class, WebController.class);
|
||||||
assertNotNull(synthesizedComponent);
|
assertNotNull(synthesizedComponent);
|
||||||
|
|
||||||
|
|
@ -979,14 +1092,35 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void synthesizeAnnotationFromMapWithMinimalAttributesWithAttributeAliases() throws Exception {
|
public void synthesizeAnnotationFromMapWithMinimalAttributesWithAttributeAliases() throws Exception {
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = Collections.singletonMap("location", "test.xml");
|
||||||
map.put("location", "test.xml");
|
|
||||||
ContextConfig contextConfig = synthesizeAnnotation(map, ContextConfig.class, null);
|
ContextConfig contextConfig = synthesizeAnnotation(map, ContextConfig.class, null);
|
||||||
assertNotNull(contextConfig);
|
assertNotNull(contextConfig);
|
||||||
assertEquals("value: ", "test.xml", contextConfig.value());
|
assertEquals("value: ", "test.xml", contextConfig.value());
|
||||||
assertEquals("location: ", "test.xml", contextConfig.location());
|
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
|
@Test
|
||||||
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
|
public void synthesizeAnnotationFromMapWithMissingAttributeValue() throws Exception {
|
||||||
assertMissingTextAttribute(Collections.emptyMap());
|
assertMissingTextAttribute(Collections.emptyMap());
|
||||||
|
|
@ -994,8 +1128,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
|
public void synthesizeAnnotationFromMapWithNullAttributeValue() throws Exception {
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = Collections.singletonMap("text", null);
|
||||||
map.put("text", null);
|
|
||||||
assertTrue(map.containsKey("text"));
|
assertTrue(map.containsKey("text"));
|
||||||
assertMissingTextAttribute(map);
|
assertMissingTextAttribute(map);
|
||||||
}
|
}
|
||||||
|
|
@ -1010,8 +1143,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
|
public void synthesizeAnnotationFromMapWithAttributeOfIncorrectType() throws Exception {
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = Collections.singletonMap(VALUE, 42L);
|
||||||
map.put(VALUE, 42L);
|
|
||||||
|
|
||||||
exception.expect(IllegalArgumentException.class);
|
exception.expect(IllegalArgumentException.class);
|
||||||
exception.expectMessage(startsWith("Attributes map"));
|
exception.expectMessage(startsWith("Attributes map"));
|
||||||
|
|
@ -1183,7 +1315,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void synthesizeAnnotationWithAttributeAliasesInNestedAnnotations() throws Exception {
|
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);
|
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||||
assertNotNull(hierarchy);
|
assertNotNull(hierarchy);
|
||||||
|
|
@ -1194,18 +1326,18 @@ public class AnnotationUtilsTests {
|
||||||
ContextConfig[] configs = synthesizedHierarchy.value();
|
ContextConfig[] configs = synthesizedHierarchy.value();
|
||||||
assertNotNull(configs);
|
assertNotNull(configs);
|
||||||
assertTrue("nested annotations must be synthesized",
|
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));
|
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));
|
assertThat(values, is(expectedLocations));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void synthesizeAnnotationWithArrayOfAnnotations() throws Exception {
|
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);
|
Hierarchy hierarchy = ConfigHierarchyTestCase.class.getAnnotation(Hierarchy.class);
|
||||||
assertNotNull(hierarchy);
|
assertNotNull(hierarchy);
|
||||||
|
|
@ -1216,7 +1348,7 @@ public class AnnotationUtilsTests {
|
||||||
assertNotNull(contextConfig);
|
assertNotNull(contextConfig);
|
||||||
|
|
||||||
ContextConfig[] configs = synthesizedHierarchy.value();
|
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));
|
assertThat(locations, is(expectedLocations));
|
||||||
|
|
||||||
// Alter array returned from synthesized annotation
|
// Alter array returned from synthesized annotation
|
||||||
|
|
@ -1224,7 +1356,7 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
// Re-retrieve the array from the synthesized annotation
|
// Re-retrieve the array from the synthesized annotation
|
||||||
configs = synthesizedHierarchy.value();
|
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));
|
assertThat(values, is(expectedLocations));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1595,6 +1727,8 @@ public class AnnotationUtilsTests {
|
||||||
|
|
||||||
@AliasFor("value")
|
@AliasFor("value")
|
||||||
String location() default "";
|
String location() default "";
|
||||||
|
|
||||||
|
Class<?> klass() default Object.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
|
@ -1770,6 +1904,109 @@ public class AnnotationUtilsTests {
|
||||||
String xmlConfigFile();
|
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)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({})
|
@Target({})
|
||||||
@interface Filter {
|
@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