summaryrefslogtreecommitdiff
path: root/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java')
-rw-r--r--spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java293
1 files changed, 196 insertions, 97 deletions
diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
index 09eb167e..2fe059a5 100644
--- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
+++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
@@ -111,6 +112,7 @@ public abstract class AnnotationUtils {
*/
public static final String VALUE = "value";
+ private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
private static final Map<AnnotationCacheKey, Annotation> findAnnotationCache =
new ConcurrentReferenceHashMap<AnnotationCacheKey, Annotation>(256);
@@ -308,6 +310,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -343,6 +346,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getAnnotationsByType
@@ -388,6 +392,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -423,6 +428,7 @@ public abstract class AnnotationUtils {
* @see #getRepeatableAnnotations(AnnotatedElement, Class)
* @see #getRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see #getDeclaredRepeatableAnnotations(AnnotatedElement, Class)
+ * @see AnnotatedElementUtils#getMergedRepeatableAnnotations(AnnotatedElement, Class, Class)
* @see org.springframework.core.BridgeMethodResolver#findBridgedMethod
* @see java.lang.annotation.Repeatable
* @see java.lang.reflect.AnnotatedElement#getDeclaredAnnotationsByType
@@ -616,7 +622,7 @@ public abstract class AnnotationUtils {
static boolean isInterfaceWithAnnotatedMethods(Class<?> iface) {
Boolean found = annotatedInterfaceCache.get(iface);
if (found != null) {
- return found.booleanValue();
+ return found;
}
found = Boolean.FALSE;
for (Method ifcMethod : iface.getMethods()) {
@@ -631,7 +637,7 @@ public abstract class AnnotationUtils {
}
}
annotatedInterfaceCache.put(iface, found);
- return found.booleanValue();
+ return found;
}
/**
@@ -883,14 +889,14 @@ public abstract class AnnotationUtils {
AnnotationCacheKey cacheKey = new AnnotationCacheKey(annotationType, metaAnnotationType);
Boolean metaPresent = metaPresentCache.get(cacheKey);
if (metaPresent != null) {
- return metaPresent.booleanValue();
+ return metaPresent;
}
metaPresent = Boolean.FALSE;
if (findAnnotation(annotationType, metaAnnotationType, false) != null) {
metaPresent = Boolean.TRUE;
}
metaPresentCache.put(cacheKey, metaPresent);
- return metaPresent.booleanValue();
+ return metaPresent;
}
/**
@@ -1013,6 +1019,13 @@ public abstract class AnnotationUtils {
public static AnnotationAttributes getAnnotationAttributes(AnnotatedElement annotatedElement,
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+ return getAnnotationAttributes(
+ (Object) annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
+ }
+
+ private static AnnotationAttributes getAnnotationAttributes(Object annotatedElement,
+ Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
+
AnnotationAttributes attributes =
retrieveAnnotationAttributes(annotatedElement, annotation, classValuesAsString, nestedAnnotationsAsMap);
postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, nestedAnnotationsAsMap);
@@ -1047,7 +1060,7 @@ public abstract class AnnotationUtils {
* @since 4.2
* @see #postProcessAnnotationAttributes
*/
- static AnnotationAttributes retrieveAnnotationAttributes(AnnotatedElement annotatedElement, Annotation annotation,
+ static AnnotationAttributes retrieveAnnotationAttributes(Object annotatedElement, Annotation annotation,
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
Class<? extends Annotation> annotationType = annotation.annotationType();
@@ -1091,14 +1104,14 @@ public abstract class AnnotationUtils {
* {@code Annotation} instances
* @return the adapted value, or the original value if no adaptation is needed
*/
- static Object adaptValue(AnnotatedElement annotatedElement, Object value, boolean classValuesAsString,
+ static Object adaptValue(Object annotatedElement, Object value, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
if (classValuesAsString) {
- if (value instanceof Class) {
+ if (value instanceof Class<?>) {
return ((Class<?>) value).getName();
}
- else if (value instanceof Class[]) {
+ else if (value instanceof Class<?>[]) {
Class<?>[] clazzArray = (Class<?>[]) value;
String[] classNames = new String[clazzArray.length];
for (int i = 0; i < clazzArray.length; i++) {
@@ -1138,6 +1151,63 @@ public abstract class AnnotationUtils {
}
/**
+ * Register the annotation-declared default values for the given attributes,
+ * if available.
+ * @param attributes the annotation attributes to process
+ * @since 4.3.2
+ */
+ public static void registerDefaultValues(AnnotationAttributes attributes) {
+ // Only do defaults scanning for public annotations; we'd run into
+ // IllegalAccessExceptions otherwise, and we don't want to mess with
+ // accessibility in a SecurityManager environment.
+ Class<? extends Annotation> annotationType = attributes.annotationType();
+ if (annotationType != null && Modifier.isPublic(annotationType.getModifiers())) {
+ // Check declared default values of attributes in the annotation type.
+ for (Method annotationAttribute : getAttributeMethods(annotationType)) {
+ String attributeName = annotationAttribute.getName();
+ Object defaultValue = annotationAttribute.getDefaultValue();
+ if (defaultValue != null && !attributes.containsKey(attributeName)) {
+ if (defaultValue instanceof Annotation) {
+ defaultValue = getAnnotationAttributes((Annotation) defaultValue, false, true);
+ }
+ else if (defaultValue instanceof Annotation[]) {
+ Annotation[] realAnnotations = (Annotation[]) defaultValue;
+ AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
+ for (int i = 0; i < realAnnotations.length; i++) {
+ mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], false, true);
+ }
+ defaultValue = mappedAnnotations;
+ }
+ attributes.put(attributeName, new DefaultValueHolder(defaultValue));
+ }
+ }
+ }
+ }
+
+ /**
+ * Post-process the supplied {@link AnnotationAttributes}, preserving nested
+ * annotations as {@code Annotation} instances.
+ * <p>Specifically, this method enforces <em>attribute alias</em> semantics
+ * for annotation attributes that are annotated with {@link AliasFor @AliasFor}
+ * and replaces default value placeholders with their original default values.
+ * @param annotatedElement the element that is annotated with an annotation or
+ * annotation hierarchy from which the supplied attributes were created;
+ * may be {@code null} if unknown
+ * @param attributes the annotation attributes to post-process
+ * @param classValuesAsString whether to convert Class references into Strings (for
+ * compatibility with {@link org.springframework.core.type.AnnotationMetadata})
+ * or to preserve them as Class references
+ * @since 4.3.2
+ * @see #postProcessAnnotationAttributes(Object, AnnotationAttributes, boolean, boolean)
+ * @see #getDefaultValue(Class, String)
+ */
+ public static void postProcessAnnotationAttributes(Object annotatedElement,
+ AnnotationAttributes attributes, boolean classValuesAsString) {
+
+ postProcessAnnotationAttributes(annotatedElement, attributes, classValuesAsString, false);
+ }
+
+ /**
* Post-process the supplied {@link AnnotationAttributes}.
* <p>Specifically, this method enforces <em>attribute alias</em> semantics
* for annotation attributes that are annotated with {@link AliasFor @AliasFor}
@@ -1154,10 +1224,10 @@ public abstract class AnnotationUtils {
* {@link org.springframework.core.type.AnnotationMetadata}) or to preserve them as
* {@code Annotation} instances
* @since 4.2
- * @see #retrieveAnnotationAttributes(AnnotatedElement, Annotation, boolean, boolean)
+ * @see #retrieveAnnotationAttributes(Object, Annotation, boolean, boolean)
* @see #getDefaultValue(Class, String)
*/
- static void postProcessAnnotationAttributes(AnnotatedElement annotatedElement,
+ static void postProcessAnnotationAttributes(Object annotatedElement,
AnnotationAttributes attributes, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
// Abort?
@@ -1171,51 +1241,55 @@ public abstract class AnnotationUtils {
// circuit the search algorithms.
Set<String> valuesAlreadyReplaced = new HashSet<String>();
- // Validate @AliasFor configuration
- Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
- for (String attributeName : aliasMap.keySet()) {
- if (valuesAlreadyReplaced.contains(attributeName)) {
- continue;
- }
- Object value = attributes.get(attributeName);
- boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
-
- for (String aliasedAttributeName : aliasMap.get(attributeName)) {
- if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ if (!attributes.validated) {
+ // Validate @AliasFor configuration
+ Map<String, List<String>> aliasMap = getAttributeAliasMap(annotationType);
+ for (String attributeName : aliasMap.keySet()) {
+ if (valuesAlreadyReplaced.contains(attributeName)) {
continue;
}
+ Object value = attributes.get(attributeName);
+ boolean valuePresent = (value != null && !(value instanceof DefaultValueHolder));
- Object aliasedValue = attributes.get(aliasedAttributeName);
- boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
-
- // 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 = (annotatedElement != null ? annotatedElement.toString() : "unknown element");
- throw new AnnotationConfigurationException(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 is permitted.", annotationType.getName(), elementAsString,
- attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
- ObjectUtils.nullSafeToString(aliasedValue)));
- }
+ for (String aliasedAttributeName : aliasMap.get(attributeName)) {
+ if (valuesAlreadyReplaced.contains(aliasedAttributeName)) {
+ continue;
}
- else if (aliasPresent) {
- // Replace value with aliasedValue
- attributes.put(attributeName,
- adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(attributeName);
- }
- else {
- // Replace aliasedValue with value
- attributes.put(aliasedAttributeName,
- adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
- valuesAlreadyReplaced.add(aliasedAttributeName);
+
+ Object aliasedValue = attributes.get(aliasedAttributeName);
+ boolean aliasPresent = (aliasedValue != null && !(aliasedValue instanceof DefaultValueHolder));
+
+ // 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 =
+ (annotatedElement != null ? annotatedElement.toString() : "unknown element");
+ throw new AnnotationConfigurationException(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 is permitted.", annotationType.getName(), elementAsString,
+ attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value),
+ ObjectUtils.nullSafeToString(aliasedValue)));
+ }
+ }
+ else if (aliasPresent) {
+ // Replace value with aliasedValue
+ attributes.put(attributeName,
+ adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(attributeName);
+ }
+ else {
+ // Replace aliasedValue with value
+ attributes.put(aliasedAttributeName,
+ adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap));
+ valuesAlreadyReplaced.add(aliasedAttributeName);
+ }
}
}
}
+ attributes.validated = true;
}
// Replace any remaining placeholders with actual default values
@@ -1355,8 +1429,12 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
* @see #synthesizeAnnotation(Class)
*/
- @SuppressWarnings("unchecked")
public static <A extends Annotation> A synthesizeAnnotation(A annotation, AnnotatedElement annotatedElement) {
+ return synthesizeAnnotation(annotation, (Object) annotatedElement);
+ }
+
+ @SuppressWarnings("unchecked")
+ static <A extends Annotation> A synthesizeAnnotation(A annotation, Object annotatedElement) {
if (annotation == null) {
return null;
}
@@ -1461,7 +1539,7 @@ public abstract class AnnotationUtils {
* @see #synthesizeAnnotation(Annotation, AnnotatedElement)
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
*/
- public static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, AnnotatedElement annotatedElement) {
+ static Annotation[] synthesizeAnnotationArray(Annotation[] annotations, Object annotatedElement) {
if (annotations == null) {
return null;
}
@@ -1489,7 +1567,7 @@ public abstract class AnnotationUtils {
* {@code @AliasFor} is detected
* @since 4.2.1
* @see #synthesizeAnnotation(Map, Class, AnnotatedElement)
- * @see #synthesizeAnnotationArray(Annotation[], AnnotatedElement)
+ * @see #synthesizeAnnotationArray(Annotation[], Object)
*/
@SuppressWarnings("unchecked")
static <A extends Annotation> A[] synthesizeAnnotationArray(Map<String, Object>[] maps, Class<A> annotationType) {
@@ -1577,7 +1655,7 @@ public abstract class AnnotationUtils {
private static boolean isSynthesizable(Class<? extends Annotation> annotationType) {
Boolean synthesizable = synthesizableCache.get(annotationType);
if (synthesizable != null) {
- return synthesizable.booleanValue();
+ return synthesizable;
}
synthesizable = Boolean.FALSE;
@@ -1605,7 +1683,7 @@ public abstract class AnnotationUtils {
}
synthesizableCache.put(annotationType, synthesizable);
- return synthesizable.booleanValue();
+ return synthesizable;
}
/**
@@ -1721,6 +1799,29 @@ public abstract class AnnotationUtils {
}
/**
+ * Resolve the container type for the supplied repeatable {@code annotationType}.
+ * <p>Automatically detects a <em>container annotation</em> declared via
+ * {@link java.lang.annotation.Repeatable}. If the supplied annotation type
+ * is not annotated with {@code @Repeatable}, this method simply returns
+ * {@code null}.
+ * @since 4.2
+ */
+ @SuppressWarnings("unchecked")
+ static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
+ try {
+ Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
+ if (repeatable != null) {
+ Object value = getValue(repeatable);
+ return (Class<? extends Annotation>) value;
+ }
+ }
+ catch (Exception ex) {
+ handleIntrospectionFailure(annotationType, ex);
+ }
+ return null;
+ }
+
+ /**
* If the supplied throwable is an {@link AnnotationConfigurationException},
* it will be cast to an {@code AnnotationConfigurationException} and thrown,
* allowing it to propagate to the caller.
@@ -1774,7 +1875,7 @@ public abstract class AnnotationUtils {
/**
* Cache key for the AnnotatedElement cache.
*/
- private static class AnnotationCacheKey {
+ private static final class AnnotationCacheKey implements Comparable<AnnotationCacheKey> {
private final AnnotatedElement element;
@@ -1801,13 +1902,25 @@ public abstract class AnnotationUtils {
public int hashCode() {
return (this.element.hashCode() * 29 + this.annotationType.hashCode());
}
+
+ @Override
+ public String toString() {
+ return "@" + this.annotationType + " on " + this.element;
+ }
+
+ @Override
+ public int compareTo(AnnotationCacheKey other) {
+ int result = this.element.toString().compareTo(other.element.toString());
+ if (result == 0) {
+ result = this.annotationType.getName().compareTo(other.annotationType.getName());
+ }
+ return result;
+ }
}
private static class AnnotationCollector<A extends Annotation> {
- private static final String REPEATABLE_CLASS_NAME = "java.lang.annotation.Repeatable";
-
private final Class<A> annotationType;
private final Class<? extends Annotation> containerAnnotationType;
@@ -1825,21 +1938,6 @@ public abstract class AnnotationUtils {
this.declaredMode = declaredMode;
}
- @SuppressWarnings("unchecked")
- static Class<? extends Annotation> resolveContainerAnnotationType(Class<? extends Annotation> annotationType) {
- try {
- Annotation repeatable = getAnnotation(annotationType, REPEATABLE_CLASS_NAME);
- if (repeatable != null) {
- Object value = AnnotationUtils.getValue(repeatable);
- return (Class<? extends Annotation>) value;
- }
- }
- catch (Exception ex) {
- handleIntrospectionFailure(annotationType, ex);
- }
- return null;
- }
-
Set<A> getResult(AnnotatedElement element) {
process(element);
return Collections.unmodifiableSet(this.result);
@@ -1951,12 +2049,19 @@ public abstract class AnnotationUtils {
this.aliasedAnnotationType = (Annotation.class == aliasFor.annotation() ?
this.sourceAnnotationType : aliasFor.annotation());
this.aliasedAttributeName = getAliasedAttributeName(aliasFor, sourceAttribute);
+ if (this.aliasedAnnotationType == this.sourceAnnotationType &&
+ this.aliasedAttributeName.equals(this.sourceAttributeName)) {
+ String msg = String.format("@AliasFor declaration on attribute '%s' in annotation [%s] points to " +
+ "itself. Specify 'annotation' to point to a same-named attribute on a meta-annotation.",
+ sourceAttribute.getName(), declaringClass.getName());
+ throw new AnnotationConfigurationException(msg);
+ }
try {
this.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].",
+ "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);
@@ -1968,8 +2073,8 @@ public abstract class AnnotationUtils {
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.",
+ 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);
@@ -1978,14 +2083,14 @@ public abstract class AnnotationUtils {
if (this.isAliasPair) {
AliasFor mirrorAliasFor = this.aliasedAttribute.getAnnotation(AliasFor.class);
if (mirrorAliasFor == null) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s].",
+ 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, this.aliasedAttribute);
if (!this.sourceAttributeName.equals(mirrorAliasedAttributeName)) {
- String msg = String.format("Attribute [%s] in annotation [%s] must be declared as an @AliasFor [%s], not [%s].",
+ 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);
@@ -1994,9 +2099,10 @@ public abstract class AnnotationUtils {
Class<?> returnType = this.sourceAttribute.getReturnType();
Class<?> aliasedReturnType = this.aliasedAttribute.getReturnType();
- if (returnType != aliasedReturnType) {
- String msg = String.format("Misconfigured aliases: attribute [%s] in annotation [%s] " +
- "and attribute [%s] in annotation [%s] must declare the same return type.",
+ if (returnType != aliasedReturnType &&
+ (!aliasedReturnType.isArray() || returnType != aliasedReturnType.getComponentType())) {
+ 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);
@@ -2013,16 +2119,16 @@ public abstract class AnnotationUtils {
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.",
+ 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.",
+ 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);
@@ -2125,15 +2231,16 @@ public abstract class AnnotationUtils {
/**
* Get the name of the aliased attribute configured via the supplied
- * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute}.
+ * {@link AliasFor @AliasFor} annotation on the supplied {@code attribute},
+ * or the original attribute if no aliased one specified (indicating that
+ * the reference goes to a same-named attribute on a meta-annotation).
* <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
- * @param attribute the attribute that is annotated with {@code @AliasFor},
- * used solely for building an exception message
+ * @param attribute the attribute that is annotated with {@code @AliasFor}
* @return the name of the aliased attribute (never {@code null} or empty)
* @throws AnnotationConfigurationException if invalid configuration of
* {@code @AliasFor} is detected
@@ -2146,23 +2253,15 @@ public abstract class AnnotationUtils {
// Ensure user did not declare both 'value' and 'attribute' in @AliasFor
if (attributeDeclared && valueDeclared) {
- String msg = String.format("In @AliasFor declared on attribute [%s] in annotation [%s], attribute 'attribute' " +
+ String msg = 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);
throw new AnnotationConfigurationException(msg);
}
+ // Either explicit attribute name or pointing to same-named attribute by default
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();
+ return (StringUtils.hasText(attributeName) ? attributeName.trim() : attribute.getName());
}
@Override