Skip to content

Commit daec353

Browse files
philwebbjhoeller
authored andcommitted
Support String->Class adaptation in MergedAnnotation
Update TypeMappedAnnotation so that Strings can be used to represent Class attribute values. This will allow ASM annotation readers to present a `MergedAnnotation` instance without necessarily having the actual class values on the classpath. When the underlying value is a String, any calls to `getValue(name, String.class)` or `asMap(Adapt.CLASS_TO_STRING)` will simply return the original String. Calls that need the actual Class result (such as `getClass`) will use `Class.forName` and may throw a `ClassNotFoundException` at that point. This commit also allows an empty Object[] to be used to represent any empty primitive array. See gh-22884
1 parent e11990e commit daec353

File tree

3 files changed

+265
-36
lines changed

3 files changed

+265
-36
lines changed

spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,26 @@ static <A extends Annotation> MergedAnnotation<A> of(
562562
static <A extends Annotation> MergedAnnotation<A> of(
563563
@Nullable AnnotatedElement source, Class<A> annotationType, @Nullable Map<String, ?> attributes) {
564564

565-
return TypeMappedAnnotation.of(source, annotationType, attributes);
565+
return of(null, source, annotationType, attributes);
566+
}
567+
568+
/**
569+
* Create a new {@link MergedAnnotation} instance of the specified
570+
* annotation type with attributes values supplied by a map.
571+
* @param classLoader the class loader used to resolve class attributes
572+
* @param source the source for the annotation. This source is used only for
573+
* information and logging. It does not need to <em>actually</em> contain
574+
* the specified annotations and it will not be searched.
575+
* @param annotationType the annotation type
576+
* @param attributes the annotation attributes or {@code null} if just default
577+
* values should be used
578+
* @return a {@link MergedAnnotation} instance for the annotation and attributes
579+
*/
580+
static <A extends Annotation> MergedAnnotation<A> of(
581+
@Nullable ClassLoader classLoader, @Nullable Object source,
582+
Class<A> annotationType, @Nullable Map<String, ?> attributes) {
583+
584+
return TypeMappedAnnotation.of(classLoader, source, annotationType, attributes);
566585
}
567586

568587

spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java

Lines changed: 138 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818

1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Array;
21+
import java.lang.reflect.Member;
2122
import java.lang.reflect.Method;
23+
import java.util.Arrays;
2224
import java.util.Collections;
25+
import java.util.HashMap;
2326
import java.util.LinkedHashMap;
2427
import java.util.List;
2528
import java.util.Map;
@@ -68,8 +71,28 @@
6871
*/
6972
final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnnotation<A> {
7073

74+
private static final Object[] EMPTY_OBJECT_ARRAY = {};
75+
76+
private static final Map<Class<?>, Object> EMPTY_ARRAYS;
77+
static {
78+
Map<Class<?>, Object> emptyArrays = new HashMap<>();
79+
emptyArrays.put(String.class, new String[] {});
80+
emptyArrays.put(boolean.class, new boolean[] {});
81+
emptyArrays.put(byte.class, new byte[] {});
82+
emptyArrays.put(char.class, new char[] {});
83+
emptyArrays.put(double.class, new double[] {});
84+
emptyArrays.put(float.class, new float[] {});
85+
emptyArrays.put(int.class, new int[] {});
86+
emptyArrays.put(long.class, new long[] {});
87+
emptyArrays.put(short.class, new short[] {});
88+
EMPTY_ARRAYS = Collections.unmodifiableMap(emptyArrays);
89+
}
90+
7191
private final AnnotationTypeMapping mapping;
7292

93+
@Nullable
94+
private final ClassLoader classLoader;
95+
7396
@Nullable
7497
private final Object source;
7598

@@ -93,21 +116,23 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
93116
private String string;
94117

95118

96-
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
97-
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
98-
int aggregateIndex) {
119+
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
120+
@Nullable Object source, @Nullable Object rootAttributes,
121+
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex) {
99122

100-
this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null);
123+
this(mapping, classLoader, source, rootAttributes, valueExtractor, aggregateIndex, null);
101124
}
102125

103-
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
104-
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
105-
int aggregateIndex, @Nullable int[] resolvedRootMirrors) {
126+
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
127+
@Nullable Object source, @Nullable Object rootAttributes,
128+
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
129+
@Nullable int[] resolvedRootMirrors) {
106130

131+
this.mapping = mapping;
132+
this.classLoader = classLoader;
107133
this.source = source;
108134
this.rootAttributes = rootAttributes;
109135
this.valueExtractor = valueExtractor;
110-
this.mapping = mapping;
111136
this.aggregateIndex = aggregateIndex;
112137
this.useMergedValues = true;
113138
this.attributeFilter = null;
@@ -117,11 +142,13 @@ private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object sou
117142
mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
118143
}
119144

120-
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
121-
@Nullable Object rootAnnotation, BiFunction<Method, Object, Object> valueExtractor,
122-
int aggregateIndex, boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
145+
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
146+
@Nullable Object source, @Nullable Object rootAnnotation,
147+
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
148+
boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
123149
int[] resolvedRootMirrors, int[] resolvedMirrors) {
124150

151+
this.classLoader = classLoader;
125152
this.source = source;
126153
this.rootAttributes = rootAnnotation;
127154
this.valueExtractor = valueExtractor;
@@ -173,7 +200,7 @@ public MergedAnnotation<?> getParent() {
173200
if (parentMapping == null) {
174201
return null;
175202
}
176-
return new TypeMappedAnnotation<>(parentMapping, this.source, this.rootAttributes,
203+
return new TypeMappedAnnotation<>(parentMapping, this.classLoader, this.source, this.rootAttributes,
177204
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
178205
}
179206

@@ -183,7 +210,7 @@ public MergedAnnotation<?> getRoot() {
183210
return this;
184211
}
185212
AnnotationTypeMapping rootMapping = this.mapping.getRoot();
186-
return new TypeMappedAnnotation<>(rootMapping, this.source, this.rootAttributes,
213+
return new TypeMappedAnnotation<>(rootMapping, this.classLoader, this.source, this.rootAttributes,
187214
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
188215
}
189216

@@ -204,7 +231,7 @@ public <T extends Annotation> MergedAnnotation<T> getAnnotation(String attribute
204231
Assert.notNull(type, "Type must not be null");
205232
Assert.isAssignable(type, attribute.getReturnType(),
206233
() -> "Attribute " + attributeName + " type mismatch:");
207-
return (MergedAnnotation<T>) getRequiredValue(attributeIndex);
234+
return (MergedAnnotation<T>) getRequiredValue(attributeIndex, attributeName);
208235
}
209236

210237
@Override
@@ -218,7 +245,7 @@ public <T extends Annotation> MergedAnnotation<T>[] getAnnotationArray(
218245
Assert.notNull(type, "Type must not be null");
219246
Assert.notNull(componentType, () -> "Attribute " + attributeName + " is not an array");
220247
Assert.isAssignable(type, componentType, () -> "Attribute " + attributeName + " component type mismatch:");
221-
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex);
248+
return (MergedAnnotation<T>[]) getRequiredValue(attributeIndex, attributeName);
222249
}
223250

224251
@Override
@@ -236,14 +263,14 @@ public MergedAnnotation<A> filterAttributes(Predicate<String> predicate) {
236263
if (this.attributeFilter != null) {
237264
predicate = this.attributeFilter.and(predicate);
238265
}
239-
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
266+
return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
240267
this.valueExtractor, this.aggregateIndex, this.useMergedValues, predicate,
241268
this.resolvedRootMirrors, this.resolvedMirrors);
242269
}
243270

244271
@Override
245272
public MergedAnnotation<A> withNonMergedAttributes() {
246-
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
273+
return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
247274
this.valueExtractor, this.aggregateIndex, false, this.attributeFilter,
248275
this.resolvedRootMirrors, this.resolvedMirrors);
249276
}
@@ -359,10 +386,11 @@ protected <T> T getAttributeValue(String attributeName, Class<T> type) {
359386
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
360387
}
361388

362-
private final Object getRequiredValue(int attributeIndex) {
389+
private final Object getRequiredValue(int attributeIndex, String attributeName) {
363390
Object value = getValue(attributeIndex, Object.class);
364391
if (value == null) {
365-
throw new NoSuchElementException("No element at attribute index " + attributeIndex);
392+
throw new NoSuchElementException("No element at attribute index "
393+
+ attributeIndex + " for name " + attributeName);
366394
}
367395
return value;
368396
}
@@ -400,7 +428,8 @@ private Object getValue(int attributeIndex, boolean useConventionMapping, boolea
400428
}
401429
if (mapping.getDepth() == 0) {
402430
Method attribute = mapping.getAttributes().get(attributeIndex);
403-
return this.valueExtractor.apply(attribute, this.rootAttributes);
431+
Object result = this.valueExtractor.apply(attribute, this.rootAttributes);
432+
return (result != null) ? result : attribute.getDefaultValue();
404433
}
405434
return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
406435
}
@@ -430,12 +459,13 @@ private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) {
430459
return null;
431460
}
432461
value = adaptForAttribute(attribute, value);
433-
if (type == Object.class) {
434-
type = (Class<T>) getDefaultAdaptType(attribute);
435-
}
436-
else if (value instanceof Class && type == String.class) {
462+
type = getAdaptType(attribute, type);
463+
if (value instanceof Class && type == String.class) {
437464
value = ((Class<?>) value).getName();
438465
}
466+
else if (value instanceof String && type == Class.class) {
467+
value = ClassUtils.resolveClassName((String) value, getClassLoader());
468+
}
439469
else if (value instanceof Class[] && type == String[].class) {
440470
Class<?>[] classes = (Class[]) value;
441471
String[] names = new String[classes.length];
@@ -444,6 +474,14 @@ else if (value instanceof Class[] && type == String[].class) {
444474
}
445475
value = names;
446476
}
477+
else if (value instanceof String[] && type == Class[].class) {
478+
String[] names = (String[]) value;
479+
Class<?>[] classes = new Class<?>[names.length];
480+
for (int i = 0; i < names.length; i++) {
481+
classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader());
482+
}
483+
value = classes;
484+
}
447485
else if (value instanceof MergedAnnotation && type.isAnnotation()) {
448486
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
449487
value = annotation.synthesize();
@@ -484,9 +522,14 @@ private Object adaptForAttribute(Method attribute, Object value) {
484522
return result;
485523
}
486524
if ((attributeType == Class.class && value instanceof String) ||
487-
(attributeType == Class[].class && value instanceof String[])) {
525+
(attributeType == Class[].class && value instanceof String[]) ||
526+
(attributeType == String.class && value instanceof Class) ||
527+
(attributeType == String[].class && value instanceof Class[])) {
488528
return value;
489529
}
530+
if(attributeType.isArray() && isEmptyObjectArray(value)) {
531+
return emptyArray(attributeType.getComponentType());
532+
}
490533
if (!attributeType.isInstance(value)) {
491534
throw new IllegalStateException("Attribute '" + attribute.getName() +
492535
"' in annotation " + getType().getName() + " should be compatible with " +
@@ -496,9 +539,24 @@ private Object adaptForAttribute(Method attribute, Object value) {
496539
return value;
497540
}
498541

542+
private boolean isEmptyObjectArray(Object value) {
543+
return value instanceof Object[] && Arrays.equals((Object[]) value, EMPTY_OBJECT_ARRAY);
544+
}
545+
546+
private Object emptyArray(Class<?> componentType) {
547+
Object result = EMPTY_ARRAYS.get(componentType);
548+
if (result == null) {
549+
result = Array.newInstance(componentType, 0);
550+
}
551+
return result;
552+
}
553+
499554
private MergedAnnotation<?> adaptToMergedAnnotation(Object value, Class<? extends Annotation> annotationType) {
555+
if (value instanceof MergedAnnotation) {
556+
return (MergedAnnotation<?>) value;
557+
}
500558
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
501-
return new TypeMappedAnnotation<>(mapping, this.source, value, getValueExtractor(value), this.aggregateIndex);
559+
return new TypeMappedAnnotation<>(mapping, null, this.source, value, getValueExtractor(value), this.aggregateIndex);
502560
}
503561

504562
private BiFunction<Method, Object, Object> getValueExtractor(Object value) {
@@ -511,15 +569,19 @@ private BiFunction<Method, Object, Object> getValueExtractor(Object value) {
511569
return this.valueExtractor;
512570
}
513571

514-
private Class<?> getDefaultAdaptType(Method attribute) {
572+
@SuppressWarnings("unchecked")
573+
private <T> Class<T> getAdaptType(Method attribute, Class<T> type) {
574+
if (type != Object.class) {
575+
return type;
576+
}
515577
Class<?> attributeType = attribute.getReturnType();
516578
if (attributeType.isAnnotation()) {
517-
return MergedAnnotation.class;
579+
return (Class<T>) MergedAnnotation.class;
518580
}
519581
if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) {
520-
return MergedAnnotation[].class;
582+
return (Class<T>) MergedAnnotation[].class;
521583
}
522-
return ClassUtils.resolvePrimitiveIfNecessary(attributeType);
584+
return (Class<T>) ClassUtils.resolvePrimitiveIfNecessary(attributeType);
523585
}
524586

525587
private int getAttributeIndex(String attributeName, boolean required) {
@@ -540,30 +602,71 @@ private boolean isFiltered(String attributeName) {
540602
return false;
541603
}
542604

605+
@Nullable
606+
private ClassLoader getClassLoader() {
607+
if (this.classLoader != null) {
608+
return this.classLoader;
609+
}
610+
if (this.source != null) {
611+
if (this.source instanceof Class) {
612+
return ((Class<?>) source).getClassLoader();
613+
}
614+
if (this.source instanceof Member) {
615+
((Member) this.source).getDeclaringClass().getClassLoader();
616+
}
617+
}
618+
return null;
619+
}
620+
543621

544622
static <A extends Annotation> MergedAnnotation<A> from(@Nullable Object source, A annotation) {
545623
Assert.notNull(annotation, "Annotation must not be null");
546624
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotation.annotationType());
547-
return new TypeMappedAnnotation<>(mappings.get(0), source, annotation, ReflectionUtils::invokeMethod, 0);
625+
return new TypeMappedAnnotation<>(mappings.get(0), null, source, annotation, ReflectionUtils::invokeMethod, 0);
548626
}
549627

550-
static <A extends Annotation> MergedAnnotation<A> of(@Nullable Object source,
628+
static <A extends Annotation> MergedAnnotation<A> of(
629+
@Nullable ClassLoader classLoader, @Nullable Object source,
551630
Class<A> annotationType, @Nullable Map<String, ?> attributes) {
552631

553632
Assert.notNull(annotationType, "Annotation type must not be null");
554633
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
555634
return new TypeMappedAnnotation<>(
556-
mappings.get(0), source, attributes, TypeMappedAnnotation::extractFromMap, 0);
635+
mappings.get(0), classLoader, source, attributes, TypeMappedAnnotation::extractFromMap, 0);
636+
}
637+
638+
@Nullable
639+
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
640+
AnnotationTypeMapping mapping, MergedAnnotation<?> annotation, IntrospectionFailureLogger logger) {
641+
if (annotation instanceof TypeMappedAnnotation) {
642+
TypeMappedAnnotation<?> typeMappedAnnotation = (TypeMappedAnnotation<?>) annotation;
643+
return createIfPossible(mapping, typeMappedAnnotation.source,
644+
typeMappedAnnotation.rootAttributes,
645+
typeMappedAnnotation.valueExtractor,
646+
typeMappedAnnotation.aggregateIndex, logger);
647+
}
648+
return createIfPossible(mapping, annotation.getSource(), annotation.synthesize(),
649+
annotation.getAggregateIndex(), logger);
557650
}
558651

559652
@Nullable
560653
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
561654
AnnotationTypeMapping mapping, @Nullable Object source, Annotation annotation,
562655
int aggregateIndex, IntrospectionFailureLogger logger) {
563656

657+
return createIfPossible(mapping, source, annotation,
658+
ReflectionUtils::invokeMethod, aggregateIndex, logger);
659+
}
660+
661+
@Nullable
662+
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
663+
AnnotationTypeMapping mapping, @Nullable Object source, Object rootAttribute,
664+
BiFunction<Method, Object, Object> valueExtractor,
665+
int aggregateIndex, IntrospectionFailureLogger logger) {
666+
564667
try {
565-
return new TypeMappedAnnotation<>(mapping, source, annotation,
566-
ReflectionUtils::invokeMethod, aggregateIndex);
668+
return new TypeMappedAnnotation<>(mapping, null, source, rootAttribute,
669+
valueExtractor, aggregateIndex);
567670
}
568671
catch (Exception ex) {
569672
if (ex instanceof AnnotationConfigurationException) {

0 commit comments

Comments
 (0)