Browse Source

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
pull/393/merge
Phillip Webb 13 years ago
parent
commit
e80b7d1e22
  1. 56
      spring-core/src/main/java/org/springframework/core/ResolvableType.java
  2. 51
      spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java
  3. 11
      spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java
  4. 17
      spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java
  5. 10
      spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

56
spring-core/src/main/java/org/springframework/core/ResolvableType.java

@ -30,6 +30,9 @@ import java.lang.reflect.WildcardType; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;

51
spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java

@ -77,7 +77,7 @@ abstract class SerializableTypeWrapper { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();

11
spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java

@ -19,6 +19,7 @@ package org.springframework.core.convert; @@ -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 { @@ -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.
* <p>If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged.

17
spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java

@ -786,6 +786,21 @@ public class ResolvableTypeTests { @@ -786,6 +786,21 @@ public class ResolvableTypeTests {
assertThat(ResolvableType.forClass(List.class, ListOfGenericArray.class).toString(), equalTo("java.util.List<java.util.List<java.lang.String>[]>"));
}
@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 { @@ -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)));
}

10
spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java

@ -24,6 +24,7 @@ import java.lang.annotation.ElementType; @@ -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; @@ -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 { @@ -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));
}
}

Loading…
Cancel
Save