From e80b7d1e2206bfcca0592d6cf2e33c8fc608a0a8 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 23 Oct 2013 14:25:24 -0700 Subject: [PATCH] Add getSource() to ResolvableType & TypeDescriptor Add getSource() method to ResolvableType and TypeDescriptor allowing access to the underlying source field or method parameter when possible. Primarily added to provide access to additional type information or meta-data that alternative JVM languages may provide. Issue: SPR-10887 --- .../springframework/core/ResolvableType.java | 56 ++++++++++++++++--- .../core/SerializableTypeWrapper.java | 51 ++++++++++++++--- .../core/convert/TypeDescriptor.java | 11 ++++ .../core/ResolvableTypeTests.java | 17 +++++- .../core/convert/TypeDescriptorTests.java | 10 +++- 5 files changed, 126 insertions(+), 19 deletions(-) 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[]>")); } + @Test + public void getSource() throws Exception { + Class classType = MySimpleInterfaceType.class; + Field basicField = Fields.class.getField("classType"); + Field field = Fields.class.getField("charSequenceList"); + Method method = Methods.class.getMethod("charSequenceParameter", List.class); + MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, 0); + assertThat(ResolvableType.forField(basicField).getSource(), equalTo((Object) basicField)); + assertThat(ResolvableType.forField(field).getSource(), equalTo((Object) field)); + assertThat(ResolvableType.forMethodParameter(methodParameter).getSource(), equalTo((Object) methodParameter)); + assertThat(ResolvableType.forMethodParameter(method, 0).getSource(), equalTo((Object) methodParameter)); + assertThat(ResolvableType.forClass(classType).getSource(), equalTo((Object) classType)); + assertThat(ResolvableType.forClass(classType).getSuperType().getSource(), equalTo((Object) classType.getGenericSuperclass())); + } + private void assertFieldToStringValue(String field, String expected) throws Exception { ResolvableType type = ResolvableType.forField(Fields.class.getField(field)); assertThat("field " + field + " toString", type.toString(), equalTo(expected)); @@ -1033,7 +1048,7 @@ public class ResolvableTypeTests { assertThat(forClass, not(equalTo(forFieldWithImplementation))); assertThat(forFieldDirect, equalTo(forFieldDirect)); - assertThat(forFieldDirect, equalTo(forFieldViaType)); + assertThat(forFieldDirect, not(equalTo(forFieldViaType))); assertThat(forFieldDirect, not(equalTo(forFieldWithImplementation))); } diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index c7de2615338..9ad7ef41fe3 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -24,6 +24,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -34,7 +35,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.junit.Ignore; import org.junit.Test; import org.springframework.core.MethodParameter; import org.springframework.util.LinkedMultiValueMap; @@ -932,4 +932,12 @@ public class TypeDescriptorTests { assertThat(typeDescriptor.getMapValueTypeDescriptor(), nullValue()); } + @Test + public void getSource() throws Exception { + Field field = getClass().getField("fieldScalar"); + MethodParameter methodParameter = new MethodParameter(getClass().getMethod("testParameterPrimitive", int.class), 0); + assertThat(new TypeDescriptor(field).getSource(), equalTo((Object) field)); + assertThat(new TypeDescriptor(methodParameter).getSource(), equalTo((Object) methodParameter)); + assertThat(TypeDescriptor.valueOf(Integer.class).getSource(), equalTo((Object) Integer.class)); + } }