|
|
|
@ -18,8 +18,10 @@ package org.springframework.core.convert; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.Serializable; |
|
|
|
import java.io.Serializable; |
|
|
|
import java.lang.annotation.Annotation; |
|
|
|
import java.lang.annotation.Annotation; |
|
|
|
|
|
|
|
import java.lang.reflect.AnnotatedElement; |
|
|
|
import java.lang.reflect.Field; |
|
|
|
import java.lang.reflect.Field; |
|
|
|
import java.lang.reflect.Type; |
|
|
|
import java.lang.reflect.Type; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.Collection; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
@ -27,7 +29,7 @@ import java.util.stream.Stream; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.core.MethodParameter; |
|
|
|
import org.springframework.core.MethodParameter; |
|
|
|
import org.springframework.core.ResolvableType; |
|
|
|
import org.springframework.core.ResolvableType; |
|
|
|
import org.springframework.core.annotation.AnnotationUtils; |
|
|
|
import org.springframework.core.annotation.AnnotatedElementUtils; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.Assert; |
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
import org.springframework.util.ClassUtils; |
|
|
|
import org.springframework.util.ObjectUtils; |
|
|
|
import org.springframework.util.ObjectUtils; |
|
|
|
@ -66,7 +68,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
|
|
|
|
|
|
|
|
private final ResolvableType resolvableType; |
|
|
|
private final ResolvableType resolvableType; |
|
|
|
|
|
|
|
|
|
|
|
private final Annotation[] annotations; |
|
|
|
private final AnnotatedElement annotatedElement; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -79,9 +81,8 @@ public class TypeDescriptor implements Serializable { |
|
|
|
Assert.notNull(methodParameter, "MethodParameter must not be null"); |
|
|
|
Assert.notNull(methodParameter, "MethodParameter must not be null"); |
|
|
|
this.resolvableType = ResolvableType.forMethodParameter(methodParameter); |
|
|
|
this.resolvableType = ResolvableType.forMethodParameter(methodParameter); |
|
|
|
this.type = this.resolvableType.resolve(methodParameter.getParameterType()); |
|
|
|
this.type = this.resolvableType.resolve(methodParameter.getParameterType()); |
|
|
|
this.annotations = (methodParameter.getParameterIndex() == -1 ? |
|
|
|
this.annotatedElement = new AnnotatedElementAdapter(methodParameter.getParameterIndex() == -1 ? |
|
|
|
nullSafeAnnotations(methodParameter.getMethodAnnotations()) : |
|
|
|
methodParameter.getMethodAnnotations() : methodParameter.getParameterAnnotations()); |
|
|
|
nullSafeAnnotations(methodParameter.getParameterAnnotations())); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -93,7 +94,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
Assert.notNull(field, "Field must not be null"); |
|
|
|
Assert.notNull(field, "Field must not be null"); |
|
|
|
this.resolvableType = ResolvableType.forField(field); |
|
|
|
this.resolvableType = ResolvableType.forField(field); |
|
|
|
this.type = this.resolvableType.resolve(field.getType()); |
|
|
|
this.type = this.resolvableType.resolve(field.getType()); |
|
|
|
this.annotations = nullSafeAnnotations(field.getAnnotations()); |
|
|
|
this.annotatedElement = new AnnotatedElementAdapter(field.getAnnotations()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -106,7 +107,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
Assert.notNull(property, "Property must not be null"); |
|
|
|
Assert.notNull(property, "Property must not be null"); |
|
|
|
this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter()); |
|
|
|
this.resolvableType = ResolvableType.forMethodParameter(property.getMethodParameter()); |
|
|
|
this.type = this.resolvableType.resolve(property.getType()); |
|
|
|
this.type = this.resolvableType.resolve(property.getType()); |
|
|
|
this.annotations = nullSafeAnnotations(property.getAnnotations()); |
|
|
|
this.annotatedElement = new AnnotatedElementAdapter(property.getAnnotations()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -120,14 +121,10 @@ public class TypeDescriptor implements Serializable { |
|
|
|
protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) { |
|
|
|
protected TypeDescriptor(ResolvableType resolvableType, Class<?> type, Annotation[] annotations) { |
|
|
|
this.resolvableType = resolvableType; |
|
|
|
this.resolvableType = resolvableType; |
|
|
|
this.type = (type != null ? type : resolvableType.resolve(Object.class)); |
|
|
|
this.type = (type != null ? type : resolvableType.resolve(Object.class)); |
|
|
|
this.annotations = nullSafeAnnotations(annotations); |
|
|
|
this.annotatedElement = new AnnotatedElementAdapter(annotations); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Annotation[] nullSafeAnnotations(Annotation[] annotations) { |
|
|
|
|
|
|
|
return (annotations != null ? annotations : EMPTY_ANNOTATION_ARRAY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Variation of {@link #getType()} that accounts for a primitive type by |
|
|
|
* Variation of {@link #getType()} that accounts for a primitive type by |
|
|
|
* returning its object wrapper type. |
|
|
|
* returning its object wrapper type. |
|
|
|
@ -189,8 +186,8 @@ public class TypeDescriptor implements Serializable { |
|
|
|
if (value == null) { |
|
|
|
if (value == null) { |
|
|
|
return this; |
|
|
|
return this; |
|
|
|
} |
|
|
|
} |
|
|
|
ResolvableType narrowed = ResolvableType.forType(value.getClass(), this.resolvableType); |
|
|
|
ResolvableType narrowed = ResolvableType.forType(value.getClass(), getResolvableType()); |
|
|
|
return new TypeDescriptor(narrowed, null, this.annotations); |
|
|
|
return new TypeDescriptor(narrowed, null, getAnnotations()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -206,7 +203,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
Assert.isAssignable(superType, getType()); |
|
|
|
Assert.isAssignable(superType, getType()); |
|
|
|
return new TypeDescriptor(this.resolvableType.as(superType), superType, this.annotations); |
|
|
|
return new TypeDescriptor(getResolvableType().as(superType), superType, getAnnotations()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -228,7 +225,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @return the annotations, or an empty array if none |
|
|
|
* @return the annotations, or an empty array if none |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public Annotation[] getAnnotations() { |
|
|
|
public Annotation[] getAnnotations() { |
|
|
|
return this.annotations; |
|
|
|
return this.annotatedElement.getAnnotations(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -239,7 +236,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @return <tt>true</tt> if the annotation is present |
|
|
|
* @return <tt>true</tt> if the annotation is present |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public boolean hasAnnotation(Class<? extends Annotation> annotationType) { |
|
|
|
public boolean hasAnnotation(Class<? extends Annotation> annotationType) { |
|
|
|
return (getAnnotation(annotationType) != null); |
|
|
|
return AnnotatedElementUtils.isAnnotated(this.annotatedElement, annotationType); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -250,22 +247,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
public <T extends Annotation> T getAnnotation(Class<T> annotationType) { |
|
|
|
public <T extends Annotation> T getAnnotation(Class<T> annotationType) { |
|
|
|
// Search in annotations that are "present" (i.e., locally declared or inherited)
|
|
|
|
return AnnotatedElementUtils.getMergedAnnotation(this.annotatedElement, annotationType); |
|
|
|
// NOTE: this unfortunately favors inherited annotations over locally declared composed annotations.
|
|
|
|
|
|
|
|
for (Annotation annotation : getAnnotations()) { |
|
|
|
|
|
|
|
if (annotation.annotationType() == annotationType) { |
|
|
|
|
|
|
|
return (T) annotation; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Search in annotation hierarchy
|
|
|
|
|
|
|
|
for (Annotation composedAnnotation : getAnnotations()) { |
|
|
|
|
|
|
|
T ann = AnnotationUtils.findAnnotation(composedAnnotation.annotationType(), annotationType); |
|
|
|
|
|
|
|
if (ann != null) { |
|
|
|
|
|
|
|
return ann; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -333,13 +315,13 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Collection} or array type |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public TypeDescriptor getElementTypeDescriptor() { |
|
|
|
public TypeDescriptor getElementTypeDescriptor() { |
|
|
|
if (this.resolvableType.isArray()) { |
|
|
|
if (getResolvableType().isArray()) { |
|
|
|
return new TypeDescriptor(this.resolvableType.getComponentType(), null, this.annotations); |
|
|
|
return new TypeDescriptor(getResolvableType().getComponentType(), null, getAnnotations()); |
|
|
|
} |
|
|
|
} |
|
|
|
if (Stream.class.isAssignableFrom(this.type)) { |
|
|
|
if (Stream.class.isAssignableFrom(getType())) { |
|
|
|
return getRelatedIfResolvable(this, this.resolvableType.as(Stream.class).getGeneric(0)); |
|
|
|
return getRelatedIfResolvable(this, getResolvableType().as(Stream.class).getGeneric(0)); |
|
|
|
} |
|
|
|
} |
|
|
|
return getRelatedIfResolvable(this, this.resolvableType.asCollection().getGeneric(0)); |
|
|
|
return getRelatedIfResolvable(this, getResolvableType().asCollection().getGeneric(0)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -380,8 +362,8 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Map} |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Map} |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public TypeDescriptor getMapKeyTypeDescriptor() { |
|
|
|
public TypeDescriptor getMapKeyTypeDescriptor() { |
|
|
|
Assert.state(isMap(), "Not a java.util.Map"); |
|
|
|
Assert.state(isMap(), "Not a [java.util.Map]"); |
|
|
|
return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(0)); |
|
|
|
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(0)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -415,8 +397,8 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Map} |
|
|
|
* @throws IllegalStateException if this type is not a {@code java.util.Map} |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public TypeDescriptor getMapValueTypeDescriptor() { |
|
|
|
public TypeDescriptor getMapValueTypeDescriptor() { |
|
|
|
Assert.state(isMap(), "Not a java.util.Map"); |
|
|
|
Assert.state(isMap(), "Not a [java.util.Map]"); |
|
|
|
return getRelatedIfResolvable(this, this.resolvableType.asMap().getGeneric(1)); |
|
|
|
return getRelatedIfResolvable(this, getResolvableType().asMap().getGeneric(1)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -444,7 +426,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
if (typeDescriptor != null) { |
|
|
|
if (typeDescriptor != null) { |
|
|
|
return typeDescriptor.narrow(value); |
|
|
|
return typeDescriptor.narrow(value); |
|
|
|
} |
|
|
|
} |
|
|
|
return (value != null ? new TypeDescriptor(this.resolvableType, value.getClass(), this.annotations) : null); |
|
|
|
return (value != null ? new TypeDescriptor(getResolvableType(), value.getClass(), getAnnotations()) : null); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@ -490,7 +472,7 @@ public class TypeDescriptor implements Serializable { |
|
|
|
for (Annotation ann : getAnnotations()) { |
|
|
|
for (Annotation ann : getAnnotations()) { |
|
|
|
builder.append("@").append(ann.annotationType().getName()).append(' '); |
|
|
|
builder.append("@").append(ann.annotationType().getName()).append(' '); |
|
|
|
} |
|
|
|
} |
|
|
|
builder.append(this.resolvableType.toString()); |
|
|
|
builder.append(getResolvableType().toString()); |
|
|
|
return builder.toString(); |
|
|
|
return builder.toString(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -525,9 +507,9 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @return the collection type descriptor |
|
|
|
* @return the collection type descriptor |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) { |
|
|
|
public static TypeDescriptor collection(Class<?> collectionType, TypeDescriptor elementTypeDescriptor) { |
|
|
|
Assert.notNull(collectionType, "collectionType must not be null"); |
|
|
|
Assert.notNull(collectionType, "Collection type must not be null"); |
|
|
|
if (!Collection.class.isAssignableFrom(collectionType)) { |
|
|
|
if (!Collection.class.isAssignableFrom(collectionType)) { |
|
|
|
throw new IllegalArgumentException("collectionType must be a java.util.Collection"); |
|
|
|
throw new IllegalArgumentException("Collection type must be a [java.util.Collection]"); |
|
|
|
} |
|
|
|
} |
|
|
|
ResolvableType element = (elementTypeDescriptor != null ? elementTypeDescriptor.resolvableType : null); |
|
|
|
ResolvableType element = (elementTypeDescriptor != null ? elementTypeDescriptor.resolvableType : null); |
|
|
|
return new TypeDescriptor(ResolvableType.forClassWithGenerics(collectionType, element), null, null); |
|
|
|
return new TypeDescriptor(ResolvableType.forClassWithGenerics(collectionType, element), null, null); |
|
|
|
@ -548,8 +530,9 @@ public class TypeDescriptor implements Serializable { |
|
|
|
* @return the map type descriptor |
|
|
|
* @return the map type descriptor |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) { |
|
|
|
public static TypeDescriptor map(Class<?> mapType, TypeDescriptor keyTypeDescriptor, TypeDescriptor valueTypeDescriptor) { |
|
|
|
|
|
|
|
Assert.notNull(mapType, "Map type must not be null"); |
|
|
|
if (!Map.class.isAssignableFrom(mapType)) { |
|
|
|
if (!Map.class.isAssignableFrom(mapType)) { |
|
|
|
throw new IllegalArgumentException("mapType must be a java.util.Map"); |
|
|
|
throw new IllegalArgumentException("Map type must be a [java.util.Map]"); |
|
|
|
} |
|
|
|
} |
|
|
|
ResolvableType key = (keyTypeDescriptor != null ? keyTypeDescriptor.resolvableType : null); |
|
|
|
ResolvableType key = (keyTypeDescriptor != null ? keyTypeDescriptor.resolvableType : null); |
|
|
|
ResolvableType value = (valueTypeDescriptor != null ? valueTypeDescriptor.resolvableType : null); |
|
|
|
ResolvableType value = (valueTypeDescriptor != null ? valueTypeDescriptor.resolvableType : null); |
|
|
|
@ -687,7 +670,60 @@ public class TypeDescriptor implements Serializable { |
|
|
|
if (type.resolve() == null) { |
|
|
|
if (type.resolve() == null) { |
|
|
|
return null; |
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
return new TypeDescriptor(type, null, source.annotations); |
|
|
|
return new TypeDescriptor(type, null, source.getAnnotations()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Adapter class for exposing a {@code TypeDescriptor}'s annotations as an |
|
|
|
|
|
|
|
* {@link AnnotatedElement}, in particular to {@link AnnotatedElementUtils}. |
|
|
|
|
|
|
|
* @see AnnotatedElementUtils#isAnnotated(AnnotatedElement, Class) |
|
|
|
|
|
|
|
* @see AnnotatedElementUtils#getMergedAnnotation(AnnotatedElement, Class) |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private class AnnotatedElementAdapter implements AnnotatedElement, Serializable { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final Annotation[] annotations; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public AnnotatedElementAdapter(Annotation[] annotations) { |
|
|
|
|
|
|
|
this.annotations = annotations; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
@SuppressWarnings("unchecked") |
|
|
|
|
|
|
|
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { |
|
|
|
|
|
|
|
for (Annotation annotation : getAnnotations()) { |
|
|
|
|
|
|
|
if (annotation.annotationType() == annotationClass) { |
|
|
|
|
|
|
|
return (T) annotation; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public Annotation[] getAnnotations() { |
|
|
|
|
|
|
|
return (this.annotations != null ? this.annotations : EMPTY_ANNOTATION_ARRAY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public Annotation[] getDeclaredAnnotations() { |
|
|
|
|
|
|
|
return getAnnotations(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public boolean equals(Object other) { |
|
|
|
|
|
|
|
return (this == other || (other instanceof AnnotatedElementAdapter && |
|
|
|
|
|
|
|
Arrays.equals(this.annotations, ((AnnotatedElementAdapter) other).annotations))); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public int hashCode() { |
|
|
|
|
|
|
|
return Arrays.hashCode(this.annotations); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String toString() { |
|
|
|
|
|
|
|
return TypeDescriptor.this.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|