diff --git a/spring-core/src/main/java/org/springframework/core/ResolvableType.java b/spring-core/src/main/java/org/springframework/core/ResolvableType.java index 2821a4f4229..c58bd31e342 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -30,6 +30,9 @@ import java.lang.reflect.WildcardType; import java.util.Collection; import java.util.Map; +import org.springframework.core.SerializableTypeWrapper.FieldTypeProvider; +import org.springframework.core.SerializableTypeWrapper.MethodParameterTypeProvider; +import org.springframework.core.SerializableTypeWrapper.TypeProvider; import org.springframework.util.Assert; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; @@ -83,7 +86,7 @@ public final class ResolvableType implements Serializable { * {@code ResolvableType} returned when no value is available. {@code NONE} is used * in preference to {@code null} so that multiple method calls can be safely chained. */ - public static final ResolvableType NONE = new ResolvableType(null, null, null); + public static final ResolvableType NONE = new ResolvableType(null, null, null, null); private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0]; @@ -94,6 +97,11 @@ public final class ResolvableType implements Serializable { */ private final Type type; + /** + * Optional provider for the type. + */ + private TypeProvider typeProvider; + /** * The {@link VariableResolver} to use or {@code null} if no resolver is available. */ @@ -121,8 +129,10 @@ public final class ResolvableType implements Serializable { * @param variableResolver the resolver used for {@link TypeVariable}s (may be {@code null}) * @param componentType an option declared component type for arrays (may be {@code null}) */ - private ResolvableType(Type type, VariableResolver variableResolver, ResolvableType componentType) { + private ResolvableType(Type type, TypeProvider typeProvider, + VariableResolver variableResolver, ResolvableType componentType) { this.type = type; + this.typeProvider = typeProvider; this.variableResolver = variableResolver; this.componentType = componentType; } @@ -148,6 +158,18 @@ public final class ResolvableType implements Serializable { return (rawType instanceof Class ? (Class) rawType : null); } + /** + * Return the underlying source of the resolvable type. Will return a {@link Field}, + * {@link MethodParameter} or {@link Type} depending on how the {@link ResolvableType} + * was constructed. With the exception of the {@link #NONE} constant, this method will + * never return {@code null}. This method is primarily to provide access to additional + * type information or meta-data that alternative JVM languages may provide. + */ + public Object getSource() { + Object source = (this.typeProvider == null ? null : this.typeProvider.getSource()); + return (source == null ? this.type : source); + } + /** * Determines if this {@code ResolvableType} is assignable from the specified * {@code type}. Attempts to follow the same rules as the Java compiler, considering @@ -656,6 +678,7 @@ public final class ResolvableType implements Serializable { if (obj instanceof ResolvableType) { ResolvableType other = (ResolvableType) obj; boolean equals = ObjectUtils.nullSafeEquals(this.type, other.type); + equals &= ObjectUtils.nullSafeEquals(getSource(), other.getSource()); equals &= variableResolverSourceEquals(this.variableResolver, other.variableResolver); equals &= ObjectUtils.nullSafeEquals(this.componentType, other.componentType); return equals; @@ -740,7 +763,7 @@ public final class ResolvableType implements Serializable { */ public static ResolvableType forField(Field field) { Assert.notNull(field, "Field must not be null"); - return forType(SerializableTypeWrapper.forField(field)); + return forType(null, new FieldTypeProvider(field), null); } /** @@ -756,7 +779,7 @@ public final class ResolvableType implements Serializable { public static ResolvableType forField(Field field, Class> implementationClass) { Assert.notNull(field, "Field must not be null"); ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); - return forType(SerializableTypeWrapper.forField(field), owner.asVariableResolver()); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()); } /** @@ -769,7 +792,7 @@ public final class ResolvableType implements Serializable { */ public static ResolvableType forField(Field field, int nestingLevel) { Assert.notNull(field, "Field must not be null"); - return forType(SerializableTypeWrapper.forField(field)).getNested(nestingLevel); + return forType(null, new FieldTypeProvider(field), null).getNested(nestingLevel); } /** @@ -787,7 +810,7 @@ public final class ResolvableType implements Serializable { public static ResolvableType forField(Field field, int nestingLevel, Class> implementationClass) { Assert.notNull(field, "Field must not be null"); ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass()); - return forType(SerializableTypeWrapper.forField(field), owner.asVariableResolver()).getNested(nestingLevel); + return forType(null, new FieldTypeProvider(field), owner.asVariableResolver()).getNested(nestingLevel); } /** @@ -888,7 +911,7 @@ public final class ResolvableType implements Serializable { public static ResolvableType forMethodParameter(MethodParameter methodParameter) { Assert.notNull(methodParameter, "MethodParameter must not be null"); ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()); - return forType(SerializableTypeWrapper.forMethodParameter(methodParameter), + return forType(null, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel); } @@ -901,7 +924,7 @@ public final class ResolvableType implements Serializable { public static ResolvableType forArrayComponent(final ResolvableType componentType) { Assert.notNull(componentType, "ComponentType must not be null"); Class> arrayClass = Array.newInstance(componentType.resolve(), 0).getClass(); - return new ResolvableType(arrayClass, null, componentType); + return new ResolvableType(arrayClass, null, null, componentType); } /** @@ -970,11 +993,26 @@ public final class ResolvableType implements Serializable { * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} */ static ResolvableType forType(Type type, VariableResolver variableResolver) { + return forType(type, null, variableResolver); + } + + /** + * Return a {@link ResolvableType} for the specified {@link Type} backed by a given + * {@link VariableResolver}. + * @param type the source type or {@code null} + * @param typeProvider the type provider or {@code null} + * @param variableResolver the variable resolver or {@code null} + * @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver} + */ + static ResolvableType forType(Type type, TypeProvider typeProvider, VariableResolver variableResolver) { + if (type == null && typeProvider != null) { + type = SerializableTypeWrapper.forTypeProvider(typeProvider); + } if (type == null) { return NONE; } // Check the cache, we may have a ResolvableType that may have already been resolved - ResolvableType key = new ResolvableType(type, variableResolver, null); + ResolvableType key = new ResolvableType(type, typeProvider, variableResolver, null); ResolvableType resolvableType = cache.get(key); if (resolvableType == null) { resolvableType = key; diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java index 20c41b38868..6dc12c7124e 100644 --- a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -77,7 +77,7 @@ abstract class SerializableTypeWrapper { * Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}. */ public static Type forGenericSuperclass(final Class> type) { - return forTypeProvider(new TypeProvider() { + return forTypeProvider(new DefaultTypeProvider() { private static final long serialVersionUID = 1L; @@ -96,7 +96,7 @@ abstract class SerializableTypeWrapper { Type[] result = new Type[type.getGenericInterfaces().length]; for (int i = 0; i < result.length; i++) { final int index = i; - result[i] = forTypeProvider(new TypeProvider() { + result[i] = forTypeProvider(new DefaultTypeProvider() { private static final long serialVersionUID = 1L; @@ -117,7 +117,7 @@ abstract class SerializableTypeWrapper { Type[] result = new Type[type.getTypeParameters().length]; for (int i = 0; i < result.length; i++) { final int index = i; - result[i] = forTypeProvider(new TypeProvider() { + result[i] = forTypeProvider(new DefaultTypeProvider() { private static final long serialVersionUID = 1L; @@ -135,7 +135,7 @@ abstract class SerializableTypeWrapper { /** * Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} . */ - private static Type forTypeProvider(final TypeProvider provider) { + static Type forTypeProvider(final TypeProvider provider) { Assert.notNull(provider, "Provider must not be null"); if (provider.getType() instanceof Serializable || provider.getType() == null) { return provider.getType(); @@ -156,16 +156,36 @@ abstract class SerializableTypeWrapper { /** * A {@link Serializable} interface providing access to a {@link Type}. */ - private static interface TypeProvider extends Serializable { + static interface TypeProvider extends Serializable { /** * Return the (possibly non {@link Serializable}) {@link Type}. */ Type getType(); + /** + * Return the source of the type or {@code null}. + */ + Object getSource(); + } + /** + * Default implementation of {@link TypeProvider} with a {@code null} source. + */ + static abstract class DefaultTypeProvider implements TypeProvider { + + private static final long serialVersionUID = 1L; + + + @Override + public Object getSource() { + return null; + } + + } + /** * {@link Serializable} {@link InvocationHandler} used by the Proxied {@link Type}. * Provides serialization support and enhances any methods that return {@code Type} @@ -207,7 +227,7 @@ abstract class SerializableTypeWrapper { /** * {@link TypeProvider} for {@link Type}s obtained from a {@link Field}. */ - private static class FieldTypeProvider implements TypeProvider { + static class FieldTypeProvider implements TypeProvider { private static final long serialVersionUID = 1L; @@ -231,6 +251,11 @@ abstract class SerializableTypeWrapper { return this.field.getGenericType(); } + @Override + public Object getSource() { + return this.field; + } + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); @@ -249,7 +274,7 @@ abstract class SerializableTypeWrapper { /** * {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}. */ - private static class MethodParameterTypeProvider implements TypeProvider { + static class MethodParameterTypeProvider implements TypeProvider { private static final long serialVersionUID = 1L; @@ -285,6 +310,11 @@ abstract class SerializableTypeWrapper { return this.methodParameter.getGenericParameterType(); } + @Override + public Object getSource() { + return this.methodParameter; + } + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); @@ -312,7 +342,7 @@ abstract class SerializableTypeWrapper { /** * {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method. */ - private static class MethodInvokeTypeProvider implements TypeProvider { + static class MethodInvokeTypeProvider implements TypeProvider { private static final long serialVersionUID = 1L; @@ -342,6 +372,11 @@ abstract class SerializableTypeWrapper { return ((Type[])this.result)[this.index]; } + @Override + public Object getSource() { + return null; + } + private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException { inputStream.defaultReadObject(); diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index 038a91c9132..de02743f48a 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -19,6 +19,7 @@ package org.springframework.core.convert; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -141,6 +142,16 @@ public class TypeDescriptor implements Serializable { return this.type; } + /** + * Return the underlying source of the descriptor. Will return a {@link Field}, + * {@link MethodParameter} or {@link Type} depending on how the {@link TypeDescriptor} + * was constructed. This method is primarily to provide access to additional + * type information or meta-data that alternative JVM languages may provide. + */ + public Object getSource() { + return (this.resolvableType == null ? null : this.resolvableType.getSource()); + } + /** * Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value. *
If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged.
diff --git a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
index ab595a7c864..b3f92e905d5 100644
--- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
+++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
@@ -786,6 +786,21 @@ public class ResolvableTypeTests {
assertThat(ResolvableType.forClass(List.class, ListOfGenericArray.class).toString(), equalTo("java.util.List