Skip to content

Commit

Permalink
Support String->Class adaptation in MergedAnnotation
Browse files Browse the repository at this point in the history
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 spring-projectsgh-22884
  • Loading branch information
philwebb committed May 6, 2019
1 parent e6ab8a1 commit 77cd957
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,26 @@ static <A extends Annotation> MergedAnnotation<A> of(
static <A extends Annotation> MergedAnnotation<A> of(
@Nullable AnnotatedElement source, Class<A> annotationType, @Nullable Map<String, ?> attributes) {

return TypeMappedAnnotation.of(source, annotationType, attributes);
return of(null, source, annotationType, attributes);
}

/**
* Create a new {@link MergedAnnotation} instance of the specified
* annotation type with attributes values supplied by a map.
* @param classLoader the class loader used to resolve class attributes
* @param source the source for the annotation. This source is used only for
* information and logging. It does not need to <em>actually</em> contain
* the specified annotations and it will not be searched.
* @param annotationType the annotation type
* @param attributes the annotation attributes or {@code null} if just default
* values should be used
* @return a {@link MergedAnnotation} instance for the annotation and attributes
*/
static <A extends Annotation> MergedAnnotation<A> of(
@Nullable ClassLoader classLoader, @Nullable Object source,
Class<A> annotationType, @Nullable Map<String, ?> attributes) {

return TypeMappedAnnotation.of(classLoader, source, annotationType, attributes);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -68,8 +71,28 @@
*/
final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnnotation<A> {

private static final Object[] EMPTY_OBJECT_ARRAY = {};

private static final Map<Class<?>, Object> EMPTY_ARRAYS;
static {
Map<Class<?>, Object> emptyArrays = new HashMap<>();
emptyArrays.put(String.class, new String[] {});
emptyArrays.put(boolean.class, new boolean[] {});
emptyArrays.put(byte.class, new byte[] {});
emptyArrays.put(char.class, new char[] {});
emptyArrays.put(double.class, new double[] {});
emptyArrays.put(float.class, new float[] {});
emptyArrays.put(int.class, new int[] {});
emptyArrays.put(long.class, new long[] {});
emptyArrays.put(short.class, new short[] {});
EMPTY_ARRAYS = Collections.unmodifiableMap(emptyArrays);
}

private final AnnotationTypeMapping mapping;

@Nullable
private final ClassLoader classLoader;

@Nullable
private final Object source;

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


private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex) {
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex) {

this(mapping, source, rootAttributes, valueExtractor, aggregateIndex, null);
this(mapping, classLoader, source, rootAttributes, valueExtractor, aggregateIndex, null);
}

private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAttributes, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex, @Nullable int[] resolvedRootMirrors) {
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
@Nullable Object source, @Nullable Object rootAttributes,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
@Nullable int[] resolvedRootMirrors) {

this.mapping = mapping;
this.classLoader = classLoader;
this.source = source;
this.rootAttributes = rootAttributes;
this.valueExtractor = valueExtractor;
this.mapping = mapping;
this.aggregateIndex = aggregateIndex;
this.useMergedValues = true;
this.attributeFilter = null;
Expand All @@ -117,11 +142,13 @@ private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object sou
mapping.getMirrorSets().resolve(source, this, this::getValueForMirrorResolution));
}

private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable Object source,
@Nullable Object rootAnnotation, BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex, boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
private TypeMappedAnnotation(AnnotationTypeMapping mapping, @Nullable ClassLoader classLoader,
@Nullable Object source, @Nullable Object rootAnnotation,
BiFunction<Method, Object, Object> valueExtractor, int aggregateIndex,
boolean useMergedValues, @Nullable Predicate<String> attributeFilter,
int[] resolvedRootMirrors, int[] resolvedMirrors) {

this.classLoader = classLoader;
this.source = source;
this.rootAttributes = rootAnnotation;
this.valueExtractor = valueExtractor;
Expand Down Expand Up @@ -173,7 +200,7 @@ public MergedAnnotation<?> getParent() {
if (parentMapping == null) {
return null;
}
return new TypeMappedAnnotation<>(parentMapping, this.source, this.rootAttributes,
return new TypeMappedAnnotation<>(parentMapping, this.classLoader, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
}

Expand All @@ -183,7 +210,7 @@ public MergedAnnotation<?> getRoot() {
return this;
}
AnnotationTypeMapping rootMapping = this.mapping.getRoot();
return new TypeMappedAnnotation<>(rootMapping, this.source, this.rootAttributes,
return new TypeMappedAnnotation<>(rootMapping, this.classLoader, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, this.resolvedRootMirrors);
}

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

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

@Override
Expand All @@ -236,14 +263,14 @@ public MergedAnnotation<A> filterAttributes(Predicate<String> predicate) {
if (this.attributeFilter != null) {
predicate = this.attributeFilter.and(predicate);
}
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, this.useMergedValues, predicate,
this.resolvedRootMirrors, this.resolvedMirrors);
}

@Override
public MergedAnnotation<A> withNonMergedAttributes() {
return new TypeMappedAnnotation<>(this.mapping, this.source, this.rootAttributes,
return new TypeMappedAnnotation<>(this.mapping, this.classLoader, this.source, this.rootAttributes,
this.valueExtractor, this.aggregateIndex, false, this.attributeFilter,
this.resolvedRootMirrors, this.resolvedMirrors);
}
Expand Down Expand Up @@ -359,10 +386,11 @@ protected <T> T getAttributeValue(String attributeName, Class<T> type) {
return (attributeIndex != -1 ? getValue(attributeIndex, type) : null);
}

private final Object getRequiredValue(int attributeIndex) {
private final Object getRequiredValue(int attributeIndex, String attributeName) {
Object value = getValue(attributeIndex, Object.class);
if (value == null) {
throw new NoSuchElementException("No element at attribute index " + attributeIndex);
throw new NoSuchElementException("No element at attribute index "
+ attributeIndex + " for name " + attributeName);
}
return value;
}
Expand Down Expand Up @@ -400,7 +428,8 @@ private Object getValue(int attributeIndex, boolean useConventionMapping, boolea
}
if (mapping.getDepth() == 0) {
Method attribute = mapping.getAttributes().get(attributeIndex);
return this.valueExtractor.apply(attribute, this.rootAttributes);
Object result = this.valueExtractor.apply(attribute, this.rootAttributes);
return (result != null) ? result : attribute.getDefaultValue();
}
return getValueFromMetaAnnotation(attributeIndex, forMirrorResolution);
}
Expand Down Expand Up @@ -430,12 +459,13 @@ private <T> T adapt(Method attribute, @Nullable Object value, Class<T> type) {
return null;
}
value = adaptForAttribute(attribute, value);
if (type == Object.class) {
type = (Class<T>) getDefaultAdaptType(attribute);
}
else if (value instanceof Class && type == String.class) {
type = getAdaptType(attribute, type);
if (value instanceof Class && type == String.class) {
value = ((Class<?>) value).getName();
}
else if (value instanceof String && type == Class.class) {
value = ClassUtils.resolveClassName((String) value, getClassLoader());
}
else if (value instanceof Class[] && type == String[].class) {
Class<?>[] classes = (Class[]) value;
String[] names = new String[classes.length];
Expand All @@ -444,6 +474,14 @@ else if (value instanceof Class[] && type == String[].class) {
}
value = names;
}
else if (value instanceof String[] && type == Class[].class) {
String[] names = (String[]) value;
Class<?>[] classes = new Class<?>[names.length];
for (int i = 0; i < names.length; i++) {
classes[i] = ClassUtils.resolveClassName(names[i], getClassLoader());
}
value = classes;
}
else if (value instanceof MergedAnnotation && type.isAnnotation()) {
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
value = annotation.synthesize();
Expand Down Expand Up @@ -484,9 +522,14 @@ private Object adaptForAttribute(Method attribute, Object value) {
return result;
}
if ((attributeType == Class.class && value instanceof String) ||
(attributeType == Class[].class && value instanceof String[])) {
(attributeType == Class[].class && value instanceof String[]) ||
(attributeType == String.class && value instanceof Class) ||
(attributeType == String[].class && value instanceof Class[])) {
return value;
}
if(attributeType.isArray() && isEmptyObjectArray(value)) {
return emptyArray(attributeType.getComponentType());
}
if (!attributeType.isInstance(value)) {
throw new IllegalStateException("Attribute '" + attribute.getName() +
"' in annotation " + getType().getName() + " should be compatible with " +
Expand All @@ -496,9 +539,24 @@ private Object adaptForAttribute(Method attribute, Object value) {
return value;
}

private boolean isEmptyObjectArray(Object value) {
return value instanceof Object[] && Arrays.equals((Object[]) value, EMPTY_OBJECT_ARRAY);
}

private Object emptyArray(Class<?> componentType) {
Object result = EMPTY_ARRAYS.get(componentType);
if (result == null) {
result = Array.newInstance(componentType, 0);
}
return result;
}

private MergedAnnotation<?> adaptToMergedAnnotation(Object value, Class<? extends Annotation> annotationType) {
if (value instanceof MergedAnnotation) {
return (MergedAnnotation<?>) value;
}
AnnotationTypeMapping mapping = AnnotationTypeMappings.forAnnotationType(annotationType).get(0);
return new TypeMappedAnnotation<>(mapping, this.source, value, getValueExtractor(value), this.aggregateIndex);
return new TypeMappedAnnotation<>(mapping, null, this.source, value, getValueExtractor(value), this.aggregateIndex);
}

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

private Class<?> getDefaultAdaptType(Method attribute) {
@SuppressWarnings("unchecked")
private <T> Class<T> getAdaptType(Method attribute, Class<T> type) {
if (type != Object.class) {
return type;
}
Class<?> attributeType = attribute.getReturnType();
if (attributeType.isAnnotation()) {
return MergedAnnotation.class;
return (Class<T>) MergedAnnotation.class;
}
if (attributeType.isArray() && attributeType.getComponentType().isAnnotation()) {
return MergedAnnotation[].class;
return (Class<T>) MergedAnnotation[].class;
}
return ClassUtils.resolvePrimitiveIfNecessary(attributeType);
return (Class<T>) ClassUtils.resolvePrimitiveIfNecessary(attributeType);
}

private int getAttributeIndex(String attributeName, boolean required) {
Expand All @@ -540,30 +602,71 @@ private boolean isFiltered(String attributeName) {
return false;
}

@Nullable
private ClassLoader getClassLoader() {
if (this.classLoader != null) {
return this.classLoader;
}
if (this.source != null) {
if (this.source instanceof Class) {
return ((Class<?>) source).getClassLoader();
}
if (this.source instanceof Member) {
((Member) this.source).getDeclaringClass().getClassLoader();
}
}
return null;
}


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

static <A extends Annotation> MergedAnnotation<A> of(@Nullable Object source,
static <A extends Annotation> MergedAnnotation<A> of(
@Nullable ClassLoader classLoader, @Nullable Object source,
Class<A> annotationType, @Nullable Map<String, ?> attributes) {

Assert.notNull(annotationType, "Annotation type must not be null");
AnnotationTypeMappings mappings = AnnotationTypeMappings.forAnnotationType(annotationType);
return new TypeMappedAnnotation<>(
mappings.get(0), source, attributes, TypeMappedAnnotation::extractFromMap, 0);
mappings.get(0), classLoader, source, attributes, TypeMappedAnnotation::extractFromMap, 0);
}

@Nullable
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
AnnotationTypeMapping mapping, MergedAnnotation<?> annotation, IntrospectionFailureLogger logger) {
if (annotation instanceof TypeMappedAnnotation) {
TypeMappedAnnotation<?> typeMappedAnnotation = (TypeMappedAnnotation<?>) annotation;
return createIfPossible(mapping, typeMappedAnnotation.source,
typeMappedAnnotation.rootAttributes,
typeMappedAnnotation.valueExtractor,
typeMappedAnnotation.aggregateIndex, logger);
}
return createIfPossible(mapping, annotation.getSource(), annotation.synthesize(),
annotation.getAggregateIndex(), logger);
}

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

return createIfPossible(mapping, source, annotation,
ReflectionUtils::invokeMethod, aggregateIndex, logger);
}

@Nullable
static <A extends Annotation> TypeMappedAnnotation<A> createIfPossible(
AnnotationTypeMapping mapping, @Nullable Object source, Object rootAttribute,
BiFunction<Method, Object, Object> valueExtractor,
int aggregateIndex, IntrospectionFailureLogger logger) {

try {
return new TypeMappedAnnotation<>(mapping, source, annotation,
ReflectionUtils::invokeMethod, aggregateIndex);
return new TypeMappedAnnotation<>(mapping, null, source, rootAttribute,
valueExtractor, aggregateIndex);
}
catch (Exception ex) {
if (ex instanceof AnnotationConfigurationException) {
Expand Down
Loading

0 comments on commit 77cd957

Please sign in to comment.