Skip to content

Commit

Permalink
Add getSource() to ResolvableType & TypeDescriptor
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Phillip Webb committed Oct 26, 2013
1 parent 014d156 commit e80b7d1
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 19 deletions.
Expand Up @@ -30,6 +30,9 @@
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;
Expand Down Expand Up @@ -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];
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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;
}
Expand All @@ -148,6 +158,18 @@ public Class<?> getRawClass() {
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
Expand Down Expand Up @@ -656,6 +678,7 @@ public boolean equals(Object obj) {
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;
Expand Down Expand Up @@ -740,7 +763,7 @@ public static ResolvableType forClass(Class<?> sourceClass, Class<?> implementat
*/
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);
}

/**
Expand All @@ -756,7 +779,7 @@ public static ResolvableType forField(Field field) {
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());
}

/**
Expand All @@ -769,7 +792,7 @@ public static ResolvableType forField(Field field, Class<?> implementationClass)
*/
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);
}

/**
Expand All @@ -787,7 +810,7 @@ public static ResolvableType forField(Field field, int nestingLevel) {
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);
}

/**
Expand Down Expand Up @@ -888,7 +911,7 @@ public static ResolvableType forMethodParameter(Method method, int parameterInde
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);
}
Expand All @@ -901,7 +924,7 @@ public static ResolvableType forMethodParameter(MethodParameter methodParameter)
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);
}

/**
Expand Down Expand Up @@ -970,11 +993,26 @@ public static ResolvableType forType(Type type, ResolvableType owner) {
* @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;
Expand Down
Expand Up @@ -77,7 +77,7 @@ public static Type forMethodParameter(MethodParameter methodParameter) {
* 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;

Expand All @@ -96,7 +96,7 @@ public static Type[] forGenericInterfaces(final Class<?> type) {
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;

Expand All @@ -117,7 +117,7 @@ public static Type[] forTypeParameters(final Class<?> type) {
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;

Expand All @@ -135,7 +135,7 @@ public Type getType() {
/**
* 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();
Expand All @@ -156,16 +156,36 @@ private static Type forTypeProvider(final TypeProvider provider) {
/**
* 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}
Expand Down Expand Up @@ -207,7 +227,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
/**
* {@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;

Expand All @@ -231,6 +251,11 @@ public Type getType() {
return this.field.getGenericType();
}

@Override
public Object getSource() {
return this.field;
}

private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
Expand All @@ -249,7 +274,7 @@ private void readObject(ObjectInputStream inputStream) throws IOException,
/**
* {@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;

Expand Down Expand Up @@ -285,6 +310,11 @@ public Type getType() {
return this.methodParameter.getGenericParameterType();
}

@Override
public Object getSource() {
return this.methodParameter;
}

private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
Expand Down Expand Up @@ -312,7 +342,7 @@ private void readObject(ObjectInputStream inputStream) throws IOException,
/**
* {@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;

Expand Down Expand Up @@ -342,6 +372,11 @@ public Type getType() {
return ((Type[])this.result)[this.index];
}

@Override
public Object getSource() {
return null;
}

private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
Expand Down
Expand Up @@ -19,6 +19,7 @@
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;
Expand Down Expand Up @@ -141,6 +142,16 @@ public Class<?> getType() {
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.
Expand Down
Expand Up @@ -786,6 +786,21 @@ public void toStrings() throws Exception {
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));
Expand Down Expand Up @@ -1033,7 +1048,7 @@ public void hashCodeAndEquals() throws Exception {
assertThat(forClass, not(equalTo(forFieldWithImplementation)));

assertThat(forFieldDirect, equalTo(forFieldDirect));
assertThat(forFieldDirect, equalTo(forFieldViaType));
assertThat(forFieldDirect, not(equalTo(forFieldViaType)));
assertThat(forFieldDirect, not(equalTo(forFieldWithImplementation)));
}

Expand Down
Expand Up @@ -24,6 +24,7 @@
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;
Expand All @@ -34,7 +35,6 @@
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;
Expand Down Expand Up @@ -932,4 +932,12 @@ public void createMapWithNullElements() throws Exception {
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));
}
}

0 comments on commit e80b7d1

Please sign in to comment.