Support implicit attribute aliases with @AliasFor

Spring Framework 4.2 introduced support for aliases between annotation
attributes that fall into the following two categories.

1) Alias pairs: two attributes in the same annotation that use
   @AliasFor to declare that they are explicit aliases for each other.
2) Meta-annotation attribute overrides: an attribute in one annotation
   uses @AliasFor to declare that it is an explicit override of an
   attribute in a meta-annotation.

However, the existing functionality fails to support the case where two
attributes in the same annotation both use @AliasFor to declare that
they are both explicit overrides of the same attribute in the same
meta-annotation. In such scenarios, one would intuitively assume that
two such attributes would be treated as "implicit" aliases for each
other, analogous to the existing support for explicit alias pairs.
Furthermore, an annotation may potentially declare multiple aliases
that are effectively a set of implicit aliases for each other.

This commit introduces support for implicit aliases configured via
@AliasFor through an extensive overhaul of the support for alias
lookups, validation, etc. Specifically, this commit includes the
following.

- Introduced isAnnotationMetaPresent() in AnnotationUtils.

- Introduced private AliasDescriptor class in AnnotationUtils in order
  to encapsulate the parsing, validation, and comparison of both
  explicit and implicit aliases configured via @AliasFor.

- Switched from single values for alias names to lists of alias names.

- Renamed getAliasedAttributeName() to getAliasedAttributeNames() in
  AnnotationUtils.

- Converted alias map to contain lists of aliases in AnnotationUtils.

- Refactored the following to support multiple implicit aliases:
  getRequiredAttributeWithAlias() in AnnotationAttributes,
  AbstractAliasAwareAnnotationAttributeExtractor,
  MapAnnotationAttributeExtractor, MergedAnnotationAttributesProcessor
  in AnnotatedElementUtils, and postProcessAnnotationAttributes() in
  AnnotationUtils.

- Introduced numerous tests for implicit alias support, including
  AbstractAliasAwareAnnotationAttributeExtractorTestCase,
  DefaultAnnotationAttributeExtractorTests, and
  MapAnnotationAttributeExtractorTests.

- Updated Javadoc in @AliasFor regarding implicit aliases and in
  AnnotationUtils regarding "meta-present".

Issue: SPR-13345
This commit is contained in:
Sam Brannen
2015-08-02 15:35:15 +02:00
parent ff9fb9aa88
commit d40a35ba5c
12 changed files with 1368 additions and 378 deletions

View File

@@ -19,6 +19,7 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
@@ -44,7 +45,7 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
private final S source;
private final Map<String, String> attributeAliasMap;
private final Map<String, List<String>> attributeAliasMap;
/**
@@ -83,29 +84,33 @@ abstract class AbstractAliasAwareAnnotationAttributeExtractor<S> implements Anno
@Override
public final Object getAttributeValue(Method attributeMethod) {
String attributeName = attributeMethod.getName();
final String attributeName = attributeMethod.getName();
Object attributeValue = getRawAttributeValue(attributeMethod);
String aliasName = this.attributeAliasMap.get(attributeName);
if (aliasName != null) {
Object aliasValue = getRawAttributeValue(aliasName);
Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
List<String> aliasNames = this.attributeAliasMap.get(attributeName);
if (aliasNames != null) {
final Object defaultValue = AnnotationUtils.getDefaultValue(getAnnotationType(), attributeName);
for (String aliasName : aliasNames) {
if (aliasName != null) {
Object aliasValue = getRawAttributeValue(aliasName);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
throw new AnnotationConfigurationException(String.format(
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
}
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) &&
!ObjectUtils.nullSafeEquals(attributeValue, defaultValue) &&
!ObjectUtils.nullSafeEquals(aliasValue, defaultValue)) {
String elementName = (getAnnotatedElement() != null ? getAnnotatedElement().toString() : "unknown element");
throw new AnnotationConfigurationException(String.format(
"In annotation [%s] declared on %s and synthesized from [%s], attribute '%s' and its " +
"alias '%s' are present with values of [%s] and [%s], but only one is permitted.",
getAnnotationType().getName(), elementName, getSource(), attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue)));
}
// If the user didn't declare the annotation with an explicit value,
// return the value of the alias.
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
// If the user didn't declare the annotation with an explicit value,
// use the value of the alias instead.
if (ObjectUtils.nullSafeEquals(attributeValue, defaultValue)) {
attributeValue = aliasValue;
}
}
}
}

View File

@@ -29,10 +29,10 @@ import java.lang.annotation.Target;
*
* <h3>Usage Scenarios</h3>
* <ul>
* <li><strong>Aliases within an annotation</strong>: within a single
* <li><strong>Explicit aliases within an annotation</strong>: within a single
* annotation, {@code @AliasFor} can be declared on a pair of attributes to
* signal that they are interchangeable aliases for each other.</li>
* <li><strong>Alias for attribute in meta-annotation</strong>: if the
* <li><strong>Explicit alias for attribute in meta-annotation</strong>: if the
* {@link #annotation} attribute of {@code @AliasFor} is set to a different
* annotation than the one that declares it, the {@link #attribute} is
* interpreted as an alias for an attribute in a meta-annotation (i.e., an
@@ -40,6 +40,11 @@ import java.lang.annotation.Target;
* control over exactly which attributes are overridden within an annotation
* hierarchy. In fact, with {@code @AliasFor} it is even possible to declare
* an alias for the {@code value} attribute of a meta-annotation.</li>
* <li><strong>Implicit aliases within an annotation</strong>: if one or
* more attributes within an annotation are declared as explicit
* meta-annotation attribute overrides for the same attribute in the
* meta-annotation, those attributes will be treated as a set of <em>implicit</em>
* aliases for each other, analogous to explicit aliases within an annotation.</li>
* </ul>
*
* <h3>Usage Requirements</h3>
@@ -57,31 +62,44 @@ import java.lang.annotation.Target;
*
* <h3>Implementation Requirements</h3>
* <ul>
* <li><strong>Aliases within an annotation</strong>:
* <li><strong>Explicit aliases within an annotation</strong>:
* <ol>
* <li>Each attribute that makes up an aliased pair must be annotated with
* {@code @AliasFor}, and either the {@link #attribute} or the {@link #value}
* attribute must reference the <em>other</em> attribute in the pair.</li>
* {@code @AliasFor}, and either {@link #attribute} or {@link #value} must
* reference the <em>other</em> attribute in the pair.</li>
* <li>Aliased attributes must declare the same return type.</li>
* <li>Aliased attributes must declare a default value.</li>
* <li>Aliased attributes must declare the same default value.</li>
* <li>The {@link #annotation} attribute should remain set to the default.</li>
* <li>{@link #annotation} should not be declared.</li>
* </ol>
* </li>
* <li><strong>Alias for attribute in meta-annotation</strong>:
* <li><strong>Explicit alias for attribute in meta-annotation</strong>:
* <ol>
* <li>The attribute that is an alias for an attribute in a meta-annotation
* must be annotated with {@code @AliasFor}, and the {@link #attribute} must
* reference the aliased attribute in the meta-annotation.</li>
* must be annotated with {@code @AliasFor}, and {@link #attribute} must
* reference the attribute in the meta-annotation.</li>
* <li>Aliased attributes must declare the same return type.</li>
* <li>The {@link #annotation} must reference the meta-annotation.</li>
* <li>{@link #annotation} must reference the meta-annotation.</li>
* <li>The referenced meta-annotation must be <em>meta-present</em> on the
* annotation class that declares {@code @AliasFor}.</li>
* </ol>
* </li>
* <li><strong>Implicit aliases within an annotation</strong>:
* <ol>
* <li>Each attribute that belongs to the set of implicit aliases must be
* annotated with {@code @AliasFor}, and {@link #attribute} must reference
* the same attribute in the same meta-annotation.</li>
* <li>Aliased attributes must declare the same return type.</li>
* <li>Aliased attributes must declare a default value.</li>
* <li>Aliased attributes must declare the same default value.</li>
* <li>{@link #annotation} must reference the meta-annotation.</li>
* <li>The referenced meta-annotation must be <em>meta-present</em> on the
* annotation class that declares {@code @AliasFor}.</li>
* </ol>
* </li>
* </ul>
*
* <h3>Example: Aliases within an Annotation</h3>
* <h3>Example: Explicit Aliases within an Annotation</h3>
* <pre class="code"> public &#064;interface ContextConfiguration {
*
* &#064;AliasFor("locations")
@@ -93,7 +111,7 @@ import java.lang.annotation.Target;
* // ...
* }</pre>
*
* <h3>Example: Alias for Attribute in Meta-annotation</h3>
* <h3>Example: Explicit Alias for Attribute in Meta-annotation</h3>
* <pre class="code"> &#064;ContextConfiguration
* public &#064;interface MyTestConfig {
*
@@ -101,6 +119,20 @@ import java.lang.annotation.Target;
* String[] xmlFiles();
* }</pre>
*
* <h3>Example: Implicit Aliases within an Annotation</h3>
* <pre class="code"> &#064;ContextConfiguration
* public &#064;interface MyTestConfig {
*
* &#064;AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
* String[] value() default {};
*
* &#064;AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
* String[] groovyScripts() default {};
*
* &#064;AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
* String[] xmlFiles() default {};
* }</pre>
*
* <h3>Spring Annotations Supporting Attribute Aliases</h3>
* <p>As of Spring Framework 4.2, several annotations within core Spring
* have been updated to use {@code @AliasFor} to configure their internal

View File

@@ -31,7 +31,6 @@ import org.springframework.core.BridgeMethodResolver;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* General utility methods for finding annotations and meta-annotations on
@@ -957,13 +956,21 @@ public class AnnotatedElementUtils {
for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) {
String attributeName = attributeMethod.getName();
String aliasedAttributeName = AnnotationUtils.getAliasedAttributeName(attributeMethod,
targetAnnotationType);
List<String> aliases = AnnotationUtils.getAliasedAttributeNames(attributeMethod, targetAnnotationType);
// Explicit annotation attribute override declared via @AliasFor
if (StringUtils.hasText(aliasedAttributeName) && attributes.containsKey(aliasedAttributeName)) {
overrideAttribute(element, annotation, attributes, attributeName, aliasedAttributeName);
if (!aliases.isEmpty()) {
if (aliases.size() != 1) {
throw new IllegalStateException(String.format(
"Alias list for annotation attribute [%s] must contain at most one element: %s",
attributeMethod, aliases));
}
String aliasedAttributeName = aliases.get(0);
if (attributes.containsKey(aliasedAttributeName)) {
overrideAttribute(element, annotation, attributes, attributeName, aliasedAttributeName);
}
}
// Implicit annotation attribute override based on convention
else if (!AnnotationUtils.VALUE.equals(attributeName) && attributes.containsKey(attributeName)) {
overrideAttribute(element, annotation, attributes, attributeName, attributeName);

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.Assert;
@@ -422,26 +423,38 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
Assert.notNull(expectedType, "expectedType must not be null");
T attributeValue = getAttribute(attributeName, expectedType);
String aliasName = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
T aliasValue = getAttribute(aliasName, expectedType);
boolean attributeDeclared = !ObjectUtils.isEmpty(attributeValue);
boolean aliasDeclared = !ObjectUtils.isEmpty(aliasValue);
if (!ObjectUtils.nullSafeEquals(attributeValue, aliasValue) && attributeDeclared && aliasDeclared) {
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
"are present with values of [%s] and [%s], but only one is permitted.",
annotationType.getName(), elementName, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
List<String> aliasNames = AnnotationUtils.getAttributeAliasMap(annotationType).get(attributeName);
if (aliasNames != null) {
for (String aliasName : aliasNames) {
T aliasValue = getAttribute(aliasName, expectedType);
boolean attributeEmpty = ObjectUtils.isEmpty(attributeValue);
boolean aliasEmpty = ObjectUtils.isEmpty(aliasValue);
if (!attributeEmpty && !aliasEmpty && !ObjectUtils.nullSafeEquals(attributeValue, aliasValue)) {
String elementName = (annotationSource == null ? "unknown element" : annotationSource.toString());
String msg = String.format("In annotation [%s] declared on [%s], attribute [%s] and its alias [%s] " +
"are present with values of [%s] and [%s], but only one is permitted.",
annotationType.getName(), elementName, attributeName, aliasName,
ObjectUtils.nullSafeToString(attributeValue), ObjectUtils.nullSafeToString(aliasValue));
throw new AnnotationConfigurationException(msg);
}
// If we expect an array and the current tracked value is null but the
// current alias value is non-null, then replace the current null value
// with the non-null value (which may be an empty array).
if (expectedType.isArray() && attributeValue == null && aliasValue != null) {
attributeValue = aliasValue;
}
// Else: if we're not expecting an array, we can rely on the behavior of
// ObjectUtils.isEmpty().
else if (attributeEmpty && !aliasEmpty) {
attributeValue = aliasValue;
}
}
assertAttributePresence(attributeName, aliasNames, attributeValue);
}
if (!attributeDeclared) {
attributeValue = aliasValue;
}
assertAttributePresence(attributeName, aliasName, attributeValue);
return attributeValue;
}
@@ -473,11 +486,11 @@ public class AnnotationAttributes extends LinkedHashMap<String, Object> {
}
}
private void assertAttributePresence(String attributeName, String aliasName, Object attributeValue) {
private void assertAttributePresence(String attributeName, List<String> aliases, Object attributeValue) {
if (attributeValue == null) {
throw new IllegalArgumentException(String.format(
"Neither attribute '%s' nor its alias '%s' was found in attributes for annotation [%s]",
attributeName, aliasName, this.displayName));
"Neither attribute '%s' nor one of its aliases %s was found in attributes for annotation [%s]",
attributeName, aliases, this.displayName));
}
}

View File

@@ -67,7 +67,9 @@ import org.springframework.util.StringUtils;
*
* <p>An annotation is <em>meta-present</em> on an element if the annotation
* is declared as a meta-annotation on some other annotation which is
* <em>present</em> on the element.
* <em>present</em> on the element. Annotation {@code A} is <em>meta-present</em>
* on another annotation if {@code A} is either <em>directly present</em> or
* <em>meta-present</em> on the other annotation.
*
* <h3>Meta-annotation Support</h3>
* <p>Most {@code find*()} methods and some {@code get*()} methods in this
@@ -123,11 +125,14 @@ public abstract class AnnotationUtils {
private static final Map<Class<?>, Boolean> annotatedInterfaceCache =
new ConcurrentReferenceHashMap<Class<?>, Boolean>(256);
private static final Map<AnnotationCacheKey, Boolean> metaPresentCache =
new ConcurrentReferenceHashMap<AnnotationCacheKey, Boolean>(256);
private static final Map<Class<? extends Annotation>, Boolean> synthesizableCache =
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Boolean>(256);
private static final Map<Class<? extends Annotation>, Map<String, String>> attributeAliasesCache =
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, String>>(256);
private static final Map<Class<? extends Annotation>, Map<String, List<String>>> attributeAliasesCache =
new ConcurrentReferenceHashMap<Class<? extends Annotation>, Map<String, List<String>>>(256);
private static final Map<Class<? extends Annotation>, List<Method>> attributeMethodsCache =
new ConcurrentReferenceHashMap<Class<? extends Annotation>, List<Method>>(256);
@@ -643,8 +648,22 @@ public abstract class AnnotationUtils {
* @param annotationType the type of annotation to look for
* @return the first matching annotation, or {@code null} if not found
*/
@SuppressWarnings("unchecked")
public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
return findAnnotation(clazz, annotationType, true);
}
/**
* Perform the actual work for {@link #findAnnotation(AnnotatedElement, Class)},
* honoring the {@code synthesize} flag.
* @param clazz the class to look for annotations on; never {@code null}
* @param annotationType the type of annotation to look for
* @param synthesize {@code true} if the result should be
* {@linkplain #synthesizeAnnotation(Annotation) synthesized}
* @return the first matching annotation, or {@code null} if not found
* @since 4.2.1
*/
@SuppressWarnings("unchecked")
private static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType, boolean synthesize) {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(clazz, annotationType);
A result = (A) findAnnotationCache.get(cacheKey);
if (result == null) {
@@ -653,7 +672,7 @@ public abstract class AnnotationUtils {
findAnnotationCache.put(cacheKey, result);
}
}
return synthesizeAnnotation(result, clazz);
return (synthesize ? synthesizeAnnotation(result, clazz) : result);
}
/**
@@ -833,6 +852,30 @@ public abstract class AnnotationUtils {
return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
}
/**
* Determine if an annotation of type {@code metaAnnotationType} is
* <em>meta-present</em> on the supplied {@code annotationType}.
* @param annotationType the annotation type to search on; never {@code null}
* @param metaAnnotationType the type of meta-annotation to search for
* @return {@code true} if such an annotation is meta-present
* @since 4.2.1
*/
public static boolean isAnnotationMetaPresent(Class<? extends Annotation> annotationType,
Class<? extends Annotation> metaAnnotationType) {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
Boolean metaPresent = metaPresentCache.get(cacheKey);
if (metaPresent != null) {
return metaPresent.booleanValue();
}
metaPresent = Boolean.FALSE;
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
metaPresent = Boolean.TRUE;
}
metaPresentCache.put(cacheKey, metaPresent);
return metaPresent.booleanValue();
}
/**
* Determine if the supplied {@link Annotation} is defined in the core JDK
* {@code java.lang.annotation} package.
@@ -1363,33 +1406,39 @@ public abstract class AnnotationUtils {
}
/**
* Get a map of all attribute alias pairs, declared via {@code @AliasFor}
* Get a map of all attribute aliases declared via {@code @AliasFor}
* in the supplied annotation type.
* <p>The map is keyed by attribute name with each value representing
* the name of the aliased attribute. For each entry {@code [x, y]} in
* the map there will be a corresponding {@code [y, x]} entry in the map.
* a list of names of aliased attributes.
* <p>For <em>explicit</em> alias pairs such as x and y (i.e., where x
* is an {@code @AliasFor("y")} and y is an {@code @AliasFor("x")}, there
* will be two entries in the map: {@code x -> (y)} and {@code y -> (x)}.
* <p>For <em>implicit</em> aliases (i.e., attributes that are declared
* as attribute overrides for the same attribute in the same meta-annotation),
* there will be n entries in the map. For example, if x, y, and z are
* implicit aliases, the map will contain the following entries:
* {@code x -> (y, z)}, {@code y -> (x, z)}, {@code z -> (x, y)}.
* <p>An empty return value implies that the annotation does not declare
* any attribute aliases.
* @param annotationType the annotation type to find attribute aliases in
* @return a map containing attribute alias pairs; never {@code null}
* @return a map containing attribute aliases; never {@code null}
* @since 4.2
*/
static Map<String, String> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
static Map<String, List<String>> getAttributeAliasMap(Class<? extends Annotation> annotationType) {
if (annotationType == null) {
return Collections.emptyMap();
}
Map<String, String> map = attributeAliasesCache.get(annotationType);
Map<String, List<String>> map = attributeAliasesCache.get(annotationType);
if (map != null) {
return map;
}
map = new HashMap<String, String>();
map = new HashMap<String, List<String>>();
for (Method attribute : getAttributeMethods(annotationType)) {
String attributeName = attribute.getName();
String aliasedAttributeName = getAliasedAttributeName(attribute);
if (aliasedAttributeName != null) {
map.put(attributeName, aliasedAttributeName);
List<String> aliasNames = getAliasedAttributeNames(attribute);
if (!aliasNames.isEmpty()) {
map.put(attribute.getName(), aliasNames);
}
}
@@ -1420,7 +1469,7 @@ public abstract class AnnotationUtils {
synthesizable = Boolean.FALSE;
for (Method attribute : getAttributeMethods(annotationType)) {
if (getAliasedAttributeName(attribute) != null) {
if (!getAliasedAttributeNames(attribute).isEmpty()) {
synthesizable = Boolean.TRUE;
break;
}
@@ -1446,184 +1495,85 @@ public abstract class AnnotationUtils {
}
/**
* Get the name of the aliased attribute configured via
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
* <p>This method does not resolve aliases in other annotations. In
* other words, if {@code @AliasFor} is present on the supplied
* {@code attribute} but {@linkplain AliasFor#annotation references an
* annotation} other than {@link Annotation}, this method will return
* {@code null} immediately.
* @param attribute the attribute to find an alias for
* @return the name of the aliased attribute, or {@code null} if not found
* Get the names of the aliased attributes configured via
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
* <p>This method does not resolve meta-annotation attribute overrides.
* @param attribute the attribute to find aliases for; never {@code null}
* @return the names of the aliased attributes; never {@code null}, though
* potentially <em>empty</em>
* @throws IllegalArgumentException if the supplied attribute method is
* not from an annotation, or if the supplied target type is {@link Annotation}
* {@code null} or not from an annotation
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #getAliasedAttributeName(Method, Class)
* @see #getAliasedAttributeNames(Method, Class)
*/
static String getAliasedAttributeName(Method attribute) {
return getAliasedAttributeName(attribute, (Class<? extends Annotation>) null);
static List<String> getAliasedAttributeNames(Method attribute) {
return getAliasedAttributeNames(attribute, (Class<? extends Annotation>) null);
}
/**
* Get the name of the aliased attribute configured via
* {@link AliasFor @AliasFor} on the supplied annotation {@code attribute}.
* @param attribute the attribute to find an alias for
* @param targetAnnotationType the type of annotation in which the
* Get the names of the aliased attributes configured via
* {@link AliasFor @AliasFor} for the supplied annotation {@code attribute}.
* <p>If the supplied {@code metaAnnotationType} is non-null, the
* returned list will contain at most one element.
* @param attribute the attribute to find aliases for; never {@code null}
* @param metaAnnotationType the type of meta-annotation in which an
* aliased attribute is allowed to be declared; {@code null} implies
* <em>within the same annotation</em>
* @return the name of the aliased attribute, or {@code null} if not found
* <em>within the same annotation</em> as the supplied attribute
* @return the names of the aliased attributes; never {@code null}, though
* potentially <em>empty</em>
* @throws IllegalArgumentException if the supplied attribute method is
* not from an annotation, or if the supplied target type is {@link Annotation}
* {@code null} or not from an annotation, or if the supplied meta-annotation
* type is {@link Annotation}
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
*/
@SuppressWarnings("unchecked")
static String getAliasedAttributeName(Method attribute, Class<? extends Annotation> targetAnnotationType) {
Class<?> declaringClass = attribute.getDeclaringClass();
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
Assert.isTrue(!Annotation.class.equals(targetAnnotationType),
"targetAnnotationType must not be java.lang.annotation.Annotation");
static List<String> getAliasedAttributeNames(Method attribute, Class<? extends Annotation> metaAnnotationType) {
Assert.notNull(attribute, "attribute method must not be null");
Assert.isTrue(!Annotation.class.equals(metaAnnotationType),
"metaAnnotationType must not be java.lang.annotation.Annotation");
String attributeName = attribute.getName();
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
AliasDescriptor descriptor = AliasDescriptor.from(attribute);
// Nothing to check
if (aliasFor == null) {
return null;
// No alias declared via @AliasFor?
if (descriptor == null) {
return Collections.emptyList();
}
Class<? extends Annotation> sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
Class<? extends Annotation> aliasedAnnotationType = aliasFor.annotation();
boolean searchWithinSameAnnotation = (targetAnnotationType == null);
boolean sameTargetDeclared =
(sourceAnnotationType.equals(aliasedAnnotationType) || Annotation.class.equals(aliasedAnnotationType));
// Explicit alias for a different target meta-annotation?
if (!searchWithinSameAnnotation && !targetAnnotationType.equals(aliasedAnnotationType)) {
return null;
}
String aliasedAttributeName = getAliasedAttributeName(aliasFor, attribute);
if (!StringUtils.hasText(aliasedAttributeName)) {
String msg = String.format(
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
attributeName, sourceAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
if (!sameTargetDeclared) {
// Target annotation is not meta-present?
if (findAnnotation(sourceAnnotationType, aliasedAnnotationType) == null) {
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares "
+ "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
attributeName, sourceAnnotationType.getName(), aliasedAttributeName,
aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
// Searching for explicit meta-annotation attribute override?
if (metaAnnotationType != null) {
if (descriptor.isAliasFor(metaAnnotationType)) {
return Collections.singletonList(descriptor.aliasedAttributeName);
}
}
else {
aliasedAnnotationType = sourceAnnotationType;
// Else: explicit attribute override for a different meta-annotation
return Collections.emptyList();
}
// Wrong search scope?
if (searchWithinSameAnnotation && !sameTargetDeclared) {
return null;
// Explicit alias pair?
if (descriptor.isAliasPair) {
return Collections.singletonList(descriptor.aliasedAttributeName);
}
Method aliasedAttribute;
try {
aliasedAttribute = aliasedAnnotationType.getDeclaredMethod(aliasedAttributeName);
}
catch (NoSuchMethodException ex) {
String msg = String.format(
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
attributeName, sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg, ex);
}
// Else: search for implicit aliases
List<String> aliases = new ArrayList<String>();
for (Method currentAttribute : getAttributeMethods(descriptor.sourceAnnotationType)) {
if (sameTargetDeclared) {
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
aliasedAttributeName, sourceAnnotationType.getName(), attributeName);
throw new AnnotationConfigurationException(msg);
// An attribute cannot alias itself
if (attribute.equals(currentAttribute)) {
continue;
}
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor, aliasedAttribute);
if (!attributeName.equals(mirrorAliasedAttributeName)) {
String msg = String.format(
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
aliasedAttributeName, sourceAnnotationType.getName(), attributeName, mirrorAliasedAttributeName);
throw new AnnotationConfigurationException(msg);
// If two attributes override the same attribute in the same meta-annotation,
// they are "implicit" aliases for each other.
AliasDescriptor otherDescriptor = AliasDescriptor.from(currentAttribute);
if (descriptor.equals(otherDescriptor)) {
descriptor.validateAgainst(otherDescriptor);
aliases.add(otherDescriptor.sourceAttributeName);
}
}
Class<?> returnType = attribute.getReturnType();
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
if (!returnType.equals(aliasedReturnType)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare the same return type.", attributeName,
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
if (sameTargetDeclared) {
Object defaultValue = attribute.getDefaultValue();
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
if ((defaultValue == null) || (aliasedDefaultValue == null)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare default values.", attributeName,
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
"and attribute [%s] in annotation [%s] must declare the same default value.", attributeName,
sourceAnnotationType.getName(), aliasedAttributeName, aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
}
return aliasedAttributeName;
}
/**
* Get the name of the aliased attribute configured via the supplied
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
* <p>This method returns the value of either the {@code attribute}
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
* one of the attributes has been declared.
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
* the aliased attribute name
* @param attribute the attribute that is annotated with {@code @AliasFor},
* used solely for building an exception message
* @return the name of the aliased attribute, potentially an empty string
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see #getAliasedAttributeName(Method, Class)
*/
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
String attributeName = aliasFor.attribute();
String value = aliasFor.value();
boolean attributeDeclared = StringUtils.hasText(attributeName);
boolean valueDeclared = StringUtils.hasText(value);
if (attributeDeclared && valueDeclared) {
throw new AnnotationConfigurationException(String.format(
"In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' and its alias 'value' "
+ "are present with values of [%s] and [%s], but only one is permitted.",
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value));
}
return (attributeDeclared ? attributeName : value);
return aliases;
}
/**
@@ -1677,6 +1627,7 @@ public abstract class AnnotationUtils {
* Determine if the supplied {@code method} is an annotation attribute method.
* @param method the method to check
* @return {@code true} if the method is an attribute method
* @since 4.2
*/
static boolean isAttributeMethod(Method method) {
return (method != null && method.getParameterTypes().length == 0 && method.getReturnType() != void.class);
@@ -1686,6 +1637,7 @@ public abstract class AnnotationUtils {
* Determine if the supplied method is an "annotationType" method.
* @return {@code true} if the method is an "annotationType" method
* @see Annotation#annotationType()
* @since 4.2
*/
static boolean isAnnotationTypeMethod(Method method) {
return (method != null && method.getName().equals("annotationType") && method.getParameterTypes().length == 0);
@@ -1723,40 +1675,62 @@ public abstract class AnnotationUtils {
Class<? extends Annotation> annotationType = attributes.annotationType();
// Track which attribute values have already been replaced so that we can short
// circuit the search algorithms.
Set<String> valuesAlreadyReplaced = new HashSet<String>();
// Validate @AliasFor configuration
Map<String, String> aliasMap = getAttributeAliasMap(annotationType);
Set<String> validated = new HashSet<String>();
Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
for (String attributeName : aliasMap.keySet()) {
String aliasedAttributeName = aliasMap.get(attributeName);
if (valuesAlreadyReplaced.contains(attributeName)) {
continue;
}
Object value = attributes.get(attributeName);
boolean valuePresent = (value != null && value != DEFAULT_VALUE_PLACEHOLDER);
for (String aliasedAttributeName : aliasMap.get(attributeName)) {
if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
continue;
}
if (validated.add(attributeName) && validated.add(aliasedAttributeName)) {
Object value = attributes.get(attributeName);
Object aliasedValue = attributes.get(aliasedAttributeName);
boolean aliasPresent = (aliasedValue != null && aliasedValue != DEFAULT_VALUE_PLACEHOLDER);
if (!ObjectUtils.nullSafeEquals(value, aliasedValue) && (value != DEFAULT_VALUE_PLACEHOLDER)
&& (aliasedValue != DEFAULT_VALUE_PLACEHOLDER)) {
String elementAsString = (element == null ? "unknown element" : element.toString());
String msg = String.format(
"In AnnotationAttributes for annotation [%s] declared on [%s], attribute [%s] and its alias [%s] are "
+ "declared with values of [%s] and [%s], but only one declaration is permitted.",
annotationType.getName(), elementAsString, attributeName, aliasedAttributeName,
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg);
}
// Replace default values with aliased values...
if (value == DEFAULT_VALUE_PLACEHOLDER) {
attributes.put(attributeName,
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
}
if (aliasedValue == DEFAULT_VALUE_PLACEHOLDER) {
attributes.put(aliasedAttributeName,
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
// Something to validate or replace with an alias?
if (valuePresent || aliasPresent) {
if (valuePresent && aliasPresent) {
// Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals().
if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) {
String elementAsString = (element == null ? "unknown element" : element.toString());
String msg = String.format("In AnnotationAttributes for annotation [%s] declared on [%s], "
+ "attribute [%s] and its alias [%s] are declared with values of [%s] and [%s], "
+ "but only one declaration is permitted.", annotationType.getName(),
elementAsString, attributeName, aliasedAttributeName,
ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue));
throw new AnnotationConfigurationException(msg);
}
}
else if (aliasPresent) {
// Replace value with aliasedValue
attributes.put(attributeName,
adaptValue(element, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
valuesAlreadyReplaced.add(attributeName);
}
else {
// Replace aliasedValue with value
attributes.put(aliasedAttributeName,
adaptValue(element, value, classValuesAsString, nestedAnnotationsAsMap));
valuesAlreadyReplaced.add(aliasedAttributeName);
}
}
}
}
// Replace any remaining placeholders with actual default values
for (String attributeName : attributes.keySet()) {
if (valuesAlreadyReplaced.contains(attributeName)) {
continue;
}
Object value = attributes.get(attributeName);
if (value == DEFAULT_VALUE_PLACEHOLDER) {
attributes.put(attributeName,
@@ -1933,4 +1907,248 @@ public abstract class AnnotationUtils {
}
}
/**
* {@code AliasDescriptor} encapsulates the declaration of {@code @AliasFor}
* on a given annotation attribute and includes support for validating
* the configuration of aliases (both explicit and implicit).
* @since 4.2.1
*/
private static class AliasDescriptor {
private final Method sourceAttribute;
private final Class<? extends Annotation> sourceAnnotationType;
private final String sourceAttributeName;
private final Class<? extends Annotation> aliasedAnnotationType;
private final String aliasedAttributeName;
private final boolean isAliasPair;
/**
* Create a new {@code AliasDescriptor} <em>from</em> the declaration
* of {@code @AliasFor} on the supplied annotation attribute and
* validate the configuration of {@code @AliasFor}.
* @param attribute the annotation attribute that is annotated with
* {@code @AliasFor}
* @return a new alias descriptor, or {@code null} if the attribute
* is not annotated with {@code @AliasFor}
* @see #validateAgainst(AliasDescriptor)
*/
public static AliasDescriptor from(Method attribute) {
AliasFor aliasFor = attribute.getAnnotation(AliasFor.class);
if (aliasFor == null) {
return null;
}
AliasDescriptor descriptor = new AliasDescriptor(attribute, aliasFor);
descriptor.validate();
return descriptor;
}
@SuppressWarnings("unchecked")
private AliasDescriptor(Method sourceAttribute, AliasFor aliasFor) {
Class<?> declaringClass = sourceAttribute.getDeclaringClass();
Assert.isTrue(declaringClass.isAnnotation(), "attribute method must be from an annotation");
this.sourceAttribute = sourceAttribute;
this.sourceAnnotationType = (Class<? extends Annotation>) declaringClass;
this.sourceAttributeName = this.sourceAttribute.getName();
this.aliasedAnnotationType = (Annotation.class.equals(aliasFor.annotation()) ? this.sourceAnnotationType
: aliasFor.annotation());
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, this.sourceAttribute);
this.isAliasPair = this.sourceAnnotationType.equals(this.aliasedAnnotationType);
}
private void validate() {
// Target annotation is not meta-present?
if (!this.isAliasPair && !isAnnotationMetaPresent(this.sourceAnnotationType, this.aliasedAnnotationType)) {
String msg = String.format("@AliasFor declaration on attribute [%s] in annotation [%s] declares "
+ "an alias for attribute [%s] in meta-annotation [%s] which is not meta-present.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
Method aliasedAttribute;
try {
aliasedAttribute = this.aliasedAnnotationType.getDeclaredMethod(this.aliasedAttributeName);
}
catch (NoSuchMethodException ex) {
String msg = String.format(
"Attribute [%s] in annotation [%s] is declared as an @AliasFor nonexistent attribute [%s] in annotation [%s].",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg, ex);
}
if (this.isAliasPair) {
AliasFor mirrorAliasFor = aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
String msg = String.format(
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName);
throw new AnnotationConfigurationException(msg);
}
String mirrorAliasedAttributeName = getAliasedAttributeName(mirrorAliasFor,
aliasedAttribute);
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
String msg = String.format(
"Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
this.aliasedAttributeName, this.sourceAnnotationType.getName(), this.sourceAttributeName,
mirrorAliasedAttributeName);
throw new AnnotationConfigurationException(msg);
}
}
Class<?> returnType = this.sourceAttribute.getReturnType();
Class<?> aliasedReturnType = aliasedAttribute.getReturnType();
if (!returnType.equals(aliasedReturnType)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
+ "and attribute [%s] in annotation [%s] must declare the same return type.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
this.aliasedAnnotationType.getName());
throw new AnnotationConfigurationException(msg);
}
if (this.isAliasPair) {
validateDefaultValueConfiguration(aliasedAttribute);
}
}
private void validateDefaultValueConfiguration(Method aliasedAttribute) {
Assert.notNull(aliasedAttribute, "aliasedAttribute must not be null");
Object defaultValue = this.sourceAttribute.getDefaultValue();
Object aliasedDefaultValue = aliasedAttribute.getDefaultValue();
if ((defaultValue == null) || (aliasedDefaultValue == null)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
+ "and attribute [%s] in annotation [%s] must declare default values.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
if (!ObjectUtils.nullSafeEquals(defaultValue, aliasedDefaultValue)) {
String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] "
+ "and attribute [%s] in annotation [%s] must declare the same default value.",
this.sourceAttributeName, this.sourceAnnotationType.getName(), aliasedAttribute.getName(),
aliasedAttribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
}
/**
* Validate this descriptor against the supplied descriptor.
* <p>This method only validates the configuration of default values
* for the two descriptors, since other aspects of the descriptors
* were validated when the descriptors were created.
*/
public void validateAgainst(AliasDescriptor otherDescriptor) {
validateDefaultValueConfiguration(otherDescriptor.sourceAttribute);
}
/**
* Does this descriptor represent an alias for an attribute in the
* supplied {@code targetAnnotationType}?
*/
public boolean isAliasFor(Class<? extends Annotation> targetAnnotationType) {
return targetAnnotationType.equals(this.aliasedAnnotationType);
}
/**
* Determine if this descriptor is logically equal to the supplied
* object.
* <p>Two descriptors are considered equal if the aliases they
* represent are from attributes in one annotation that alias the
* same attribute in a given target annotation.
*/
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AliasDescriptor)) {
return false;
}
AliasDescriptor that = (AliasDescriptor) other;
if (!this.sourceAnnotationType.equals(that.sourceAnnotationType)) {
return false;
}
if (!this.aliasedAnnotationType.equals(that.aliasedAnnotationType)) {
return false;
}
if (!this.aliasedAttributeName.equals(that.aliasedAttributeName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = this.sourceAnnotationType.hashCode();
result = 31 * result + this.aliasedAnnotationType.hashCode();
result = 31 * result + this.aliasedAttributeName.hashCode();
return result;
}
@Override
public String toString() {
return String.format("%s: '%s' in @%s is an alias for '%s' in @%s", getClass().getSimpleName(),
this.sourceAttributeName, this.sourceAnnotationType.getName(), this.aliasedAttributeName,
(this.aliasedAnnotationType != null ? this.aliasedAnnotationType.getName() : null));
}
/**
* Get the name of the aliased attribute configured via the supplied
* {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
* <p>This method returns the value of either the {@code attribute}
* or {@code value} attribute of {@code @AliasFor}, ensuring that only
* one of the attributes has been declared while simultaneously ensuring
* that at least one of the attributes has been declared.
* @param aliasFor the {@code @AliasFor} annotation from which to retrieve
* the aliased attribute name; never {@code null}
* @param attribute the attribute that is annotated with {@code @AliasFor},
* used solely for building an exception message; never {@code null}
* @return the name of the aliased attribute, never {@code null} or empty
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
* @since 4.2
* @see AnnotationUtils#getAliasedAttributeNames(Method, Class)
*/
private static String getAliasedAttributeName(AliasFor aliasFor, Method attribute) {
String attributeName = aliasFor.attribute();
String value = aliasFor.value();
boolean attributeDeclared = StringUtils.hasText(attributeName);
boolean valueDeclared = StringUtils.hasText(value);
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
if (attributeDeclared && valueDeclared) {
throw new AnnotationConfigurationException(String.format(
"In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' and its alias 'value' "
+ "are present with values of [%s] and [%s], but only one is permitted.",
attribute.getName(), attribute.getDeclaringClass().getName(), attributeName, value));
}
attributeName = (attributeDeclared ? attributeName : value);
// Ensure user declared either 'value' or 'attribute' in @AliasFor
if (!StringUtils.hasText(attributeName)) {
String msg = String.format(
"@AliasFor declaration on attribute [%s] in annotation [%s] is missing required 'attribute' value.",
attribute.getName(), attribute.getDeclaringClass().getName());
throw new AnnotationConfigurationException(msg);
}
return attributeName.trim();
}
}
}

View File

@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.util.ClassUtils;
@@ -87,25 +88,30 @@ class MapAnnotationAttributeExtractor extends AbstractAliasAwareAnnotationAttrib
Map<String, Object> originalAttributes, Class<? extends Annotation> annotationType) {
Map<String, Object> attributes = new HashMap<String, Object>(originalAttributes);
Map<String, String> attributeAliasMap = getAttributeAliasMap(annotationType);
Map<String, List<String>> attributeAliasMap = getAttributeAliasMap(annotationType);
for (Method attributeMethod : getAttributeMethods(annotationType)) {
String attributeName = attributeMethod.getName();
Object attributeValue = attributes.get(attributeName);
// if attribute not present, check alias
// if attribute not present, check aliases
if (attributeValue == null) {
String aliasName = attributeAliasMap.get(attributeName);
if (aliasName != null) {
Object aliasValue = attributes.get(aliasName);
if (aliasValue != null) {
attributeValue = aliasValue;
attributes.put(attributeName, attributeValue);
List<String> aliasNames = attributeAliasMap.get(attributeName);
if (aliasNames != null) {
for (String aliasName : aliasNames) {
if (aliasName != null) {
Object aliasValue = attributes.get(aliasName);
if (aliasValue != null) {
attributeValue = aliasValue;
attributes.put(attributeName, attributeValue);
break;
}
}
}
}
}
// if alias not present, check default
// if aliases not present, check default
if (attributeValue == null) {
Object defaultValue = getDefaultValue(annotationType, attributeName);
if (defaultValue != null) {