Refine ResolvableType class

- Support for serialization
- Allow programmatic creation of an array from a given component type
- Allow programmatic creation with given generics
- Extract generics from Class types using Class.getTypeParameters()
- Move TypeVariableResolver to an inner class (and make method private)
- Refine 'resolve()' algorithm

Issue: SPR-10973
This commit is contained in:
Phillip Webb 2013-10-16 23:17:22 -07:00
parent 7ffd05a8e3
commit 3337fd32cb
6 changed files with 901 additions and 200 deletions

View File

@ -256,10 +256,22 @@ public abstract class GenericTypeResolver {
* @deprecated as of Spring 4.0 in favor of {@link ResolvableType}
*/
@Deprecated
public static Class<?> resolveType(Type genericType, Map<TypeVariable, Type> typeVariableMap) {
TypeVariableResolver variableResolver = new TypeVariableMapResolver(typeVariableMap);
Class<?> resolved = ResolvableType.forType(genericType, variableResolver).resolve();
return (resolved == null ? Object.class : resolved);
public static Class<?> resolveType(Type genericType, final Map<TypeVariable, Type> typeVariableMap) {
ResolvableType.VariableResolver variableResolver = new ResolvableType.VariableResolver() {
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
Type type = typeVariableMap.get(variable);
return (type == null ? null : ResolvableType.forType(type));
}
@Override
public Object getSource() {
return typeVariableMap;
}
};
return ResolvableType.forType(genericType, variableResolver).resolve(Object.class);
}
/**
@ -303,40 +315,4 @@ public abstract class GenericTypeResolver {
}
}
/**
* Adapts a {@code typeVariableMap} to a {@link TypeVariableResolver}.
*/
private static class TypeVariableMapResolver implements TypeVariableResolver {
private final Map<TypeVariable, Type> typeVariableMap;
public TypeVariableMapResolver(Map<TypeVariable, Type> typeVariableMap) {
Assert.notNull("TypeVariableMap must not be null");
this.typeVariableMap = typeVariableMap;
}
@Override
public Type resolveVariable(TypeVariable typeVariable) {
return this.typeVariableMap.get(typeVariable);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof TypeVariableMapResolver) {
TypeVariableMapResolver other = (TypeVariableMapResolver) obj;
return this.typeVariableMap.equals(other.typeVariableMap);
}
return false;
}
@Override
public int hashCode() {
return this.typeVariableMap.hashCode();
}
}
}

View File

@ -16,6 +16,8 @@
package org.springframework.core;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@ -41,9 +43,9 @@ import org.springframework.util.StringUtils;
*
* <p>{@code ResolvableTypes} may be obtained from {@link #forField(Field) fields},
* {@link #forMethodParameter(Method, int) method parameters},
* {@link #forMethodReturnType(Method) method returns}, {@link #forClass(Class) classes}, or
* directly from a {@link #forType(Type) java.lang.reflect.Type}. Most methods on this class
* will themselves return {@link ResolvableType}s, allowing easy navigation. For example:
* {@link #forMethodReturnType(Method) method returns} or
* {@link #forClass(Class) classes}. Most methods on this class will themselves return
* {@link ResolvableType}s, allowing easy navigation. For example:
* <pre class="code">
* private HashMap&lt;Integer, List&lt;String&gt;&gt; myMap;
*
@ -61,7 +63,6 @@ import org.springframework.util.StringUtils;
* @author Phillip Webb
* @author Juergen Hoeller
* @since 4.0
* @see TypeVariableResolver
* @see #forField(Field)
* @see #forMethodParameter(Method, int)
* @see #forMethodReturnType(Method)
@ -69,7 +70,7 @@ import org.springframework.util.StringUtils;
* @see #forClass(Class)
* @see #forType(Type)
*/
public final class ResolvableType implements TypeVariableResolver {
public final class ResolvableType implements Serializable {
private static ConcurrentReferenceHashMap<ResolvableType, ResolvableType> cache =
new ConcurrentReferenceHashMap<ResolvableType, ResolvableType>();
@ -79,7 +80,7 @@ public final class ResolvableType implements TypeVariableResolver {
* {@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);
public static final ResolvableType NONE = new ResolvableType(null, null, null);
private static final ResolvableType[] EMPTY_TYPES_ARRAY = new ResolvableType[0];
@ -91,9 +92,9 @@ public final class ResolvableType implements TypeVariableResolver {
private final Type type;
/**
* The {@link TypeVariableResolver} to use or {@code null} if no resolver is available.
* The {@link VariableResolver} to use or {@code null} if no resolver is available.
*/
private final TypeVariableResolver variableResolver;
private final VariableResolver variableResolver;
/**
* Stored copy of the resolved value or {@code null} if the resolve method has not
@ -101,15 +102,23 @@ public final class ResolvableType implements TypeVariableResolver {
*/
private Class<?> resolved;
/**
* The component type for an array or {@code null} if the type should be deduced.
*/
private final ResolvableType componentType;
/**
* Private constructor used to create a new {@link ResolvableType}.
* @param type the underlying java type (may only be {@code null} for {@link #NONE})
* @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, TypeVariableResolver variableResolver) {
private ResolvableType(Type type, VariableResolver variableResolver, ResolvableType componentType) {
this.type = type;
this.variableResolver = variableResolver;
this.componentType = componentType;
}
@ -203,10 +212,12 @@ public final class ResolvableType implements TypeVariableResolver {
if (this == NONE) {
return NONE;
}
if (this.componentType != null) {
return this.componentType;
}
if (this.type instanceof Class) {
Class<?> componentType = ((Class<?>) this.type).getComponentType();
return (componentType == null ? NONE : forType(componentType,
this.variableResolver));
return forType(componentType, this.variableResolver);
}
if (this.type instanceof GenericArrayType) {
return forType(((GenericArrayType) this.type).getGenericComponentType(),
@ -272,11 +283,11 @@ public final class ResolvableType implements TypeVariableResolver {
* @see #getInterfaces()
*/
public ResolvableType getSuperType() {
Class<?> resolved = resolve();
final Class<?> resolved = resolve();
if (resolved == null || resolved.getGenericSuperclass() == null) {
return NONE;
}
return forType(resolved.getGenericSuperclass(), this);
return forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver());
}
/**
@ -286,16 +297,11 @@ public final class ResolvableType implements TypeVariableResolver {
* @see #getSuperType()
*/
public ResolvableType[] getInterfaces() {
Class<?> resolved = resolve();
final Class<?> resolved = resolve();
if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) {
return EMPTY_TYPES_ARRAY;
}
Type[] interfaceTypes = resolved.getGenericInterfaces();
ResolvableType[] interfaces = new ResolvableType[interfaceTypes.length];
for (int i = 0; i < interfaceTypes.length; i++) {
interfaces[i] = forType(interfaceTypes[i], this);
}
return interfaces;
return forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver());
}
/**
@ -407,11 +413,15 @@ public final class ResolvableType implements TypeVariableResolver {
if (this == NONE) {
return EMPTY_TYPES_ARRAY;
}
if (this.type instanceof Class<?>) {
Class<?> typeClass = (Class<?>) this.type;
return forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
}
if (this.type instanceof ParameterizedType) {
Type[] genericTypes = ((ParameterizedType) getType()).getActualTypeArguments();
ResolvableType[] generics = new ResolvableType[genericTypes.length];
for (int i = 0; i < genericTypes.length; i++) {
generics[i] = forType(genericTypes[i], this);
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
for (int i = 0; i < actualTypeArguments.length; i++) {
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
}
return generics;
}
@ -475,10 +485,8 @@ public final class ResolvableType implements TypeVariableResolver {
*/
public Class<?> resolve(Class<?> fallback) {
if (this.resolved == null) {
synchronized (this) {
this.resolved = resolveClass();
this.resolved = (this.resolved == null ? void.class : this.resolved);
}
Class<?> resolvedClass = resolveClass();
this.resolved = (resolvedClass == null ? void.class : resolvedClass);
}
return (this.resolved == void.class ? fallback : this.resolved);
}
@ -495,27 +503,40 @@ public final class ResolvableType implements TypeVariableResolver {
/**
* Resolve this type by a single level, returning the resolved value or {@link #NONE}.
* NOTE: the returned {@link ResolvableType} should only be used as an intermediary as
* it cannot be serialized.
*/
ResolvableType resolveType() {
Type resolved = null;
if (this.type instanceof ParameterizedType) {
resolved = ((ParameterizedType) this.type).getRawType();
return forType(((ParameterizedType) this.type).getRawType(),
this.variableResolver);
}
else if (this.type instanceof WildcardType) {
resolved = resolveBounds(((WildcardType) this.type).getUpperBounds());
if (this.type instanceof WildcardType) {
Type resolved = resolveBounds(((WildcardType) this.type).getUpperBounds());
if (resolved == null) {
resolved = resolveBounds(((WildcardType) this.type).getLowerBounds());
}
return forType(resolved, this.variableResolver);
}
else if (this.type instanceof TypeVariable) {
if (this.type instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) this.type;
// Try default variable resolution
if (this.variableResolver != null) {
resolved = this.variableResolver.resolveVariable((TypeVariable<?>) this.type);
}
if (resolved == null) {
resolved = resolveBounds(((TypeVariable<?>) this.type).getBounds());
ResolvableType resolved = this.variableResolver.resolveVariable(variable);
if(resolved != null) {
return resolved;
}
}
return (resolved == null ? NONE : forType(resolved, this.variableResolver));
// Fallback to bounds
return forType(resolveBounds(variable.getBounds()), this.variableResolver);
}
return NONE;
}
private Type resolveBounds(Type[] bounds) {
@ -525,31 +546,35 @@ public final class ResolvableType implements TypeVariableResolver {
return bounds[0];
}
public Type resolveVariable(TypeVariable<?> variable) {
Assert.notNull("Variable must not be null");
private ResolvableType resolveVariable(TypeVariable<?> variable) {
if (this.type instanceof TypeVariable) {
return resolveType().resolveVariable(variable);
}
if (this.type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) this.type;
Type owner = parameterizedType.getOwnerType();
if (parameterizedType.getRawType().equals(variable.getGenericDeclaration())) {
TypeVariable<?>[] variables = resolve().getTypeParameters();
for (int i = 0; i < variables.length; i++) {
if (ObjectUtils.nullSafeEquals(variables[i].getName(), variable.getName())) {
return parameterizedType.getActualTypeArguments()[i];
Type actualType = parameterizedType.getActualTypeArguments()[i];
return forType(actualType, this.variableResolver);
}
}
}
Type resolved = null;
if (parameterizedType.getOwnerType() != null) {
return forType(parameterizedType.getOwnerType(),
this.variableResolver).resolveVariable(variable);
}
}
if (this.variableResolver != null) {
resolved = this.variableResolver.resolveVariable(variable);
}
if (resolved == null && owner != null) {
resolved = forType(owner, this.variableResolver).resolveVariable(variable);
}
return resolved;
}
if (this.type instanceof TypeVariable) {
return resolveType().resolveVariable(variable);
return this.variableResolver.resolveVariable(variable);
}
return null;
}
@ -580,17 +605,63 @@ public final class ResolvableType implements TypeVariableResolver {
}
if (obj instanceof ResolvableType) {
ResolvableType other = (ResolvableType) obj;
return ObjectUtils.nullSafeEquals(this.type, other.type) &&
ObjectUtils.nullSafeEquals(this.variableResolver, other.variableResolver);
boolean equals = ObjectUtils.nullSafeEquals(this.type, other.type);
equals &= variableResolverSourceEquals(this.variableResolver, other.variableResolver);
equals &= ObjectUtils.nullSafeEquals(this.componentType, other.componentType);
return equals;
}
return false;
}
@Override
public int hashCode() {
return ObjectUtils.nullSafeHashCode(this.type);
int hashCode = ObjectUtils.nullSafeHashCode(this.type);
hashCode = hashCode * 31 + ObjectUtils.nullSafeHashCode(this.variableResolver);
hashCode = hashCode * 31 + ObjectUtils.nullSafeHashCode(this.componentType);
return hashCode;
}
/**
* Custom serialization support for {@value #NONE}.
*/
private Object readResolve() throws ObjectStreamException {
return (this.type == null ? NONE : this);
}
/**
* Adapts this {@link ResolvableType} to a {@link VariableResolver}.
*/
VariableResolver asVariableResolver() {
if (this == NONE) {
return null;
}
return new VariableResolver() {
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
return ResolvableType.this.resolveVariable(variable);
}
@Override
public Object getSource() {
return ResolvableType.this;
}
};
}
private static boolean variableResolverSourceEquals(VariableResolver o1, VariableResolver o2) {
Object s1 = (o1 == null ? null : o1.getSource());
Object s2 = (o2 == null ? null : o2.getSource());
return ObjectUtils.nullSafeEquals(s1,s2);
}
private static ResolvableType[] forTypes(Type[] types, VariableResolver owner) {
ResolvableType[] result = new ResolvableType[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = forType(types[i], owner);
}
return result;
}
/**
* Return a {@link ResolvableType} for the specified {@link Class}. For example
@ -598,6 +669,7 @@ public final class ResolvableType implements TypeVariableResolver {
* @param sourceClass the source class (must not be {@code null}
* @return a {@link ResolvableType} for the specified class
* @see #forClass(Class, Class)
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClass(Class<?> sourceClass) {
Assert.notNull(sourceClass, "Source class must not be null");
@ -609,14 +681,15 @@ public final class ResolvableType implements TypeVariableResolver {
* implementation. For example
* {@code ResolvableType.forClass(List.class, MyArrayList.class)}.
* @param sourceClass the source class (must not be {@code null}
* @param implementationClass the implementation class (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified class backed by the given
* implementation class
* @see #forClass(Class)
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClass(Class<?> sourceClass, Class<?> implementationClass) {
Assert.notNull(sourceClass, "Source class must not be null");
ResolvableType asType = (implementationClass != null ? forType(implementationClass).as(sourceClass) : NONE);
ResolvableType asType = forType(implementationClass).as(sourceClass);
return (asType == NONE ? forType(sourceClass) : asType);
}
@ -628,7 +701,7 @@ public final class ResolvableType implements TypeVariableResolver {
*/
public static ResolvableType forField(Field field) {
Assert.notNull(field, "Field must not be null");
return forType(field.getGenericType());
return forType(SerializableTypeWrapper.forField(field));
}
/**
@ -637,15 +710,14 @@ public final class ResolvableType implements TypeVariableResolver {
* <p>Use this variant when the class that declares the field includes generic
* parameter variables that are satisfied by the implementation class.
* @param field the source field
* @param implementationClass the implementation class (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, Class<?> implementationClass) {
Assert.notNull(field, "Field must not be null");
TypeVariableResolver variableResolver = (implementationClass != null ?
forType(implementationClass).as(field.getDeclaringClass()) : null);
return forType(field.getGenericType(), variableResolver);
ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
return forType(SerializableTypeWrapper.forField(field), owner.asVariableResolver());
}
/**
@ -658,7 +730,7 @@ public final class ResolvableType implements TypeVariableResolver {
*/
public static ResolvableType forField(Field field, int nestingLevel) {
Assert.notNull(field, "Field must not be null");
return forType(field.getGenericType()).getNested(nestingLevel);
return forType(SerializableTypeWrapper.forField(field)).getNested(nestingLevel);
}
/**
@ -669,15 +741,14 @@ public final class ResolvableType implements TypeVariableResolver {
* @param field the source field
* @param nestingLevel the nesting level (1 for the outer level; 2 for a nested
* generic type; etc)
* @param implementationClass the implementation class (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified field
* @see #forField(Field)
*/
public static ResolvableType forField(Field field, int nestingLevel, Class<?> implementationClass) {
Assert.notNull(field, "Field must not be null");
TypeVariableResolver variableResolver = (implementationClass != null ?
forType(implementationClass).as(field.getDeclaringClass()) : null);
return forType(field.getGenericType(), variableResolver).getNested(nestingLevel);
ResolvableType owner = forType(implementationClass).as(field.getDeclaringClass());
return forType(SerializableTypeWrapper.forField(field), owner.asVariableResolver()).getNested(nestingLevel);
}
/**
@ -699,19 +770,45 @@ public final class ResolvableType implements TypeVariableResolver {
* implementation class.
* @param constructor the source constructor (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified constructor parameter
* @see #forConstructorParameter(Constructor, int)
*/
public static ResolvableType forConstructorParameter(Constructor<?> constructor, int parameterIndex,
Class<?> implementationClass) {
Assert.notNull(constructor, "Constructor must not be null");
MethodParameter methodParameter = new MethodParameter(constructor, parameterIndex);
methodParameter.setContainingClass(implementationClass);
return forMethodParameter(methodParameter);
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* @param method the source for the method return type
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method, Class)
*/
public static ResolvableType forMethodReturnType(Method method) {
Assert.notNull(method, "Method must not be null");
return forMethodParameter(MethodParameter.forMethodOrConstructor(method, -1));
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* Use this variant when the class that declares the method includes generic
* parameter variables that are satisfied by the implementation class.
* @param method the source for the method return type
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = MethodParameter.forMethodOrConstructor(method, -1);
methodParameter.setContainingClass(implementationClass);
return forMethodParameter(methodParameter);
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} parameter.
* @param method the source method (must not be {@code null})
@ -731,12 +828,13 @@ public final class ResolvableType implements TypeVariableResolver {
* includes generic parameter variables that are satisfied by the implementation class.
* @param method the source method (must not be {@code null})
* @param parameterIndex the parameter index
* @param implementationClass the implementation class (must not be {@code null})
* @param implementationClass the implementation class
* @return a {@link ResolvableType} for the specified method parameter
* @see #forMethodParameter(Method, int, Class)
* @see #forMethodParameter(MethodParameter)
*/
public static ResolvableType forMethodParameter(Method method, int parameterIndex, Class<?> implementationClass) {
public static ResolvableType forMethodParameter(Method method, int parameterIndex,
Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
MethodParameter methodParameter = new MethodParameter(method, parameterIndex);
methodParameter.setContainingClass(implementationClass);
@ -751,59 +849,119 @@ public final class ResolvableType implements TypeVariableResolver {
*/
public static ResolvableType forMethodParameter(MethodParameter methodParameter) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
TypeVariableResolver variableResolver = (methodParameter.getContainingClass() != null ?
forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass()) : null);
return forType(methodParameter.getGenericParameterType(), variableResolver).getNested(
methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
ResolvableType owner = forType(methodParameter.getContainingClass()).as(
methodParameter.getDeclaringClass());
return forType(SerializableTypeWrapper.forMethodParameter(methodParameter),
owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(),
methodParameter.typeIndexesPerLevel);
}
/**
* Return a {@link ResolvableType} for the specified {@link Method} return type.
* @param method the source for the method return type
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method, Class)
* Return a {@link ResolvableType} as a array of the specified {@code componentType}.
* @param componentType the component type
* @return a {@link ResolvableType} as an array of the specified component type
*/
public static ResolvableType forMethodReturnType(Method method) {
Assert.notNull(method, "Method must not be null");
return forType(method.getGenericReturnType());
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 a {@link ResolvableType} for the specified {@link Method} return type.
* Use this variant when the class that declares the method includes generic
* parameter variables that are satisfied by the implementation class.
* @param method the source for the method return type
* @param implementationClass the implementation class (must not be {@code null})
* @return a {@link ResolvableType} for the specified method return
* @see #forMethodReturnType(Method)
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared
* generics.
* @param sourceClass the source class
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, ResolvableType...)
*/
public static ResolvableType forMethodReturnType(Method method, Class<?> implementationClass) {
Assert.notNull(method, "Method must not be null");
TypeVariableResolver variableResolver = (implementationClass != null ?
forType(implementationClass).as(method.getDeclaringClass()) : null);
return forType(method.getGenericReturnType(), variableResolver);
public static ResolvableType forClassWithGenerics(Class<?> sourceClass,
Class<?>... generics) {
Assert.notNull(sourceClass, "Source class must not be null");
Assert.notNull(generics, "Generics must not be null");
ResolvableType[] resolvableGenerics = new ResolvableType[generics.length];
for (int i = 0; i < generics.length; i++) {
resolvableGenerics[i] = forClass(generics[i]);
}
return forClassWithGenerics(sourceClass, resolvableGenerics);
}
/**
* Return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}.
* @param type the source type (must not be {@code null})
* @return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
* Return a {@link ResolvableType} for the specified {@link Class} with pre-declared
* generics.
* @param sourceClass the source class
* @param generics the generics of the class
* @return a {@link ResolvableType} for the specific class and generics
* @see #forClassWithGenerics(Class, Class...)
*/
public static ResolvableType forClassWithGenerics(Class<?> sourceClass,
final ResolvableType... generics) {
Assert.notNull(sourceClass, "Source class must not be null");
Assert.notNull(generics, "Generics must not be null");
final TypeVariable<?>[] typeVariables = sourceClass.getTypeParameters();
Assert.isTrue(typeVariables.length == generics.length,
"Missmatched number of generics specified");
VariableResolver variableResolver = new VariableResolver() {
@Override
public ResolvableType resolveVariable(TypeVariable<?> variable) {
for (int i = 0; i < typeVariables.length; i++) {
if(typeVariables[i].equals(variable)) {
return generics[i];
}
}
return null;
}
@Override
public Object getSource() {
return generics;
}
};
return forType(sourceClass, variableResolver);
}
/**
* Return a {@link ResolvableType} for the specified {@link Type}. NOTE: The resulting
* {@link ResolvableType} may not be {@link Serializable}.
* @param type the source type or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type}
* @see #forType(Type, ResolvableType)
*/
public static ResolvableType forType(Type type) {
return forType(type, null);
return forType(type, (VariableResolver) null);
}
/**
* Return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
* backed by a given {@link TypeVariableResolver}.
* @param type the source type (must not be {@code null})
* @param variableResolver the variable resolver
* @return a {@link ResolvableType} for the specified {@link java.lang.reflect.Type}
* and {@link TypeVariableResolver}
* Return a {@link ResolvableType} for the specified {@link Type} backed by the
* given owner type. NOTE: The resulting {@link ResolvableType} may not be
* {@link Serializable}.
* @param type the source type or {@code null}
* @param owner the owner type used to resolve variables
* @return a {@link ResolvableType} for the specified {@link Type} and owner
* @see #forType(Type)
*/
public static ResolvableType forType(Type type, TypeVariableResolver variableResolver) {
ResolvableType key = new ResolvableType(type, variableResolver);
public static ResolvableType forType(Type type, ResolvableType owner) {
VariableResolver variableResolver = null;
if (owner != null) {
variableResolver = owner.asVariableResolver();
}
return forType(type, 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 variableResolver the variable resolver or {@code null}
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
*/
static ResolvableType forType(Type type, VariableResolver variableResolver) {
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 resolvableType = cache.get(key);
if (resolvableType == null) {
resolvableType = key;
@ -813,6 +971,26 @@ public final class ResolvableType implements TypeVariableResolver {
}
/**
* Strategy interface used to resolve {@link TypeVariable}s.
*/
static interface VariableResolver extends Serializable {
/**
* Return the source of the resolver (used for hashCode and equals).
*/
Object getSource();
/**
* Resolve the specified varaible.
* @param variable the variable to resolve
* @return the resolved variable or {@code null}
*/
ResolvableType resolveVariable(TypeVariable<?> variable);
}
/**
* Internal helper to handle bounds from {@link WildcardType}s.
*/
@ -886,7 +1064,8 @@ public final class ResolvableType implements TypeVariableResolver {
Type[] bounds = boundsType == Kind.UPPER ? wildcardType.getUpperBounds() : wildcardType.getLowerBounds();
ResolvableType[] resolvableBounds = new ResolvableType[bounds.length];
for (int i = 0; i < bounds.length; i++) {
resolvableBounds[i] = forType(bounds[i], type.variableResolver);
resolvableBounds[i] = ResolvableType.forType(bounds[i],
type.variableResolver);
}
return new WildcardBounds(boundsType, resolvableBounds);
}

View File

@ -0,0 +1,330 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Internal utility class that can be used to obtain wrapped {@link Serializable} variants
* of {@link java.lang.reflect.Type}s.
*
* <p>{@link #forField(Field) Fields} or {@link #forMethodParameter(MethodParameter)
* MethodParameters} can be used as the root source for a serializable type. Alternatively
* the {@link #forGenericSuperclass(Class) superclass},
* {@link #forGenericInterfaces(Class) interfaces} or {@link #forTypeParameters(Class)
* type parameters} or a regular {@link Class} can also be used as source.
*
* <p>The returned type will either be a {@link Class} or a serializable proxy of
* {@link GenericArrayType}, {@link ParameterizedType}, {@link TypeVariable} or
* {@link WildcardType}. With the exception of {@link Class} (which is final) calls to
* methods that return further {@link Type}s (for example
* {@link GenericArrayType#getGenericComponentType()}) will be automatically wrapped.
*
* @author Phillip Webb
* @since 4.0
*/
abstract class SerializableTypeWrapper {
private static final Class<?>[] SUPPORTED_SERIALAZABLE_TYPES = { GenericArrayType.class,
ParameterizedType.class, TypeVariable.class, WildcardType.class };
/**
* Return a {@link Serializable} variant of {@link Field#getGenericType()}.
*/
public static Type forField(Field field) {
Assert.notNull(field, "Field must not be null");
return forTypeProvider(new FieldTypeProvider(field));
}
/**
* Return a {@link Serializable} variant of
* {@link MethodParameter#getGenericParameterType()}.
*/
public static Type forMethodParameter(MethodParameter methodParameter) {
return forTypeProvider(new MethodParameterTypeProvider(methodParameter));
}
/**
* Return a {@link Serializable} variant of {@link Class#getGenericSuperclass()}.
*/
public static Type forGenericSuperclass(final Class<?> type) {
return forTypeProvider(new TypeProvider() {
@Override
public Type getType() {
return type.getGenericSuperclass();
}
});
}
/**
* Return a {@link Serializable} variant of {@link Class#getGenericInterfaces()}.
*/
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() {
@Override
public Type getType() {
return type.getGenericInterfaces()[index];
}
});
}
return result;
}
/**
* Return a {@link Serializable} variant of {@link Class#getTypeParameters()}.
*/
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() {
@Override
public Type getType() {
return type.getTypeParameters()[index];
}
});
}
return result;
}
/**
* Return a {@link Serializable} {@link Type} backed by a {@link TypeProvider} .
*/
private 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();
}
for (Class<?> type : SUPPORTED_SERIALAZABLE_TYPES) {
if (type.isAssignableFrom(provider.getType().getClass())) {
ClassLoader classLoader = provider.getClass().getClassLoader();
Class<?>[] interfaces = new Class<?>[] { type, Serializable.class };
InvocationHandler handler = new TypeProxyInvocationHandler(provider);
return (Type) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
}
throw new IllegalArgumentException("Unsupported Type class "
+ provider.getType().getClass().getName());
}
/**
* A {@link Serializable} interface providing access to a {@link Type}.
*/
private static interface TypeProvider extends Serializable {
/**
* Return the (possibly non {@link Serializable}) {@link Type}.
*/
Type getType();
}
/**
* {@link Serializable} {@link InvocationHandler} used by the Proxied {@link Type}.
* Provides serialization support and enhances any methods that return {@code Type}
* or {@code Type[]}.
*/
private static class TypeProxyInvocationHandler implements InvocationHandler,
Serializable {
private final TypeProvider provider;
public TypeProxyInvocationHandler(TypeProvider provider) {
this.provider = provider;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Type.class.equals(method.getReturnType()) && args == null) {
return forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, -1));
}
if (Type[].class.equals(method.getReturnType()) && args == null) {
Type[] result = new Type[((Type[]) method.invoke(this.provider.getType(), args)).length];
for (int i = 0; i < result.length; i++) {
result[i] = forTypeProvider(new MethodInvokeTypeProvider(this.provider, method, i));
}
return result;
}
return method.invoke(this.provider.getType(), args);
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained from a {@link Field}.
*/
private static class FieldTypeProvider implements TypeProvider {
private final String fieldName;
private final Class<?> declaringClass;
private transient Field field;
public FieldTypeProvider(Field field) {
this.fieldName = field.getName();
this.declaringClass = field.getDeclaringClass();
this.field = field;
}
@Override
public Type getType() {
return this.field.getGenericType();
}
private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
try {
this.field = this.declaringClass.getDeclaredField(this.fieldName);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not find original class structure", ex);
}
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained from a {@link MethodParameter}.
*/
private static class MethodParameterTypeProvider implements TypeProvider {
private final String methodName;
private final Class<?>[] parameterTypes;
private final Class<?> declaringClass;
private final int parameterIndex;
private transient MethodParameter methodParameter;
public MethodParameterTypeProvider(MethodParameter methodParameter) {
if (methodParameter.getMethod() != null) {
this.methodName = methodParameter.getMethod().getName();
this.parameterTypes = methodParameter.getMethod().getParameterTypes();
}
else {
this.methodName = null;
this.parameterTypes = methodParameter.getConstructor().getParameterTypes();
}
this.declaringClass = methodParameter.getDeclaringClass();
this.parameterIndex = methodParameter.getParameterIndex();
this.methodParameter = methodParameter;
}
@Override
public Type getType() {
return this.methodParameter.getGenericParameterType();
}
private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
try {
if (this.methodName != null) {
this.methodParameter = new MethodParameter(
this.declaringClass.getDeclaredMethod(this.methodName,
this.parameterTypes), this.parameterIndex);
}
else {
this.methodParameter = new MethodParameter(
this.declaringClass.getDeclaredConstructor(this.parameterTypes),
this.parameterIndex);
}
}
catch (Throwable ex) {
throw new IllegalStateException(
"Could not find original class structure", ex);
}
}
}
/**
* {@link TypeProvider} for {@link Type}s obtained by invoking a no-arg method.
*/
private static class MethodInvokeTypeProvider implements TypeProvider {
private final TypeProvider provider;
private final String methodName;
private final int index;
private transient Object result;
public MethodInvokeTypeProvider(TypeProvider provider, Method method, int index) {
this.provider = provider;
this.methodName = method.getName();
this.index = index;
this.result = ReflectionUtils.invokeMethod(method, provider.getType());
}
@Override
public Type getType() {
if (this.result instanceof Type || this.result == null) {
return (Type) this.result;
}
return ((Type[])this.result)[this.index];
}
private void readObject(ObjectInputStream inputStream) throws IOException,
ClassNotFoundException {
inputStream.defaultReadObject();
Method method = ReflectionUtils.findMethod(
this.provider.getType().getClass(), this.methodName);
this.result = ReflectionUtils.invokeMethod(method, this.provider.getType());
}
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
/**
* Strategy interface that can be used to resolve {@link java.lang.reflect.TypeVariable}s.
*
* @author Phillip Webb
* @since 4.0
* @see ResolvableType
* @see GenericTypeResolver
*/
interface TypeVariableResolver {
/**
* Resolve the specified type variable.
* @param typeVariable the type variable to resolve (must not be {@code null})
* @return the resolved {@link java.lang.reflect.Type} for the variable or
* {@code null} if the variable cannot be resolved.
*/
Type resolveVariable(TypeVariable<?> typeVariable);
}

View File

@ -16,6 +16,10 @@
package org.springframework.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@ -44,12 +48,14 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.core.ResolvableType.VariableResolver;
import org.springframework.util.MultiValueMap;
import static org.mockito.BDDMockito.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Tests for {@link ResolvableType}.
@ -86,7 +92,6 @@ public class ResolvableTypeTests {
assertThat(none.resolve(String.class), equalTo((Class) String.class));
assertThat(none.resolveGeneric(0), nullValue());
assertThat(none.resolveGenerics().length, equalTo(0));
assertThat(none.resolveVariable(mock(TypeVariable.class)), nullValue());
assertThat(none.toString(), equalTo("?"));
assertThat(none.isAssignableFrom(ResolvableType.forClass(Object.class)), equalTo(false));
}
@ -415,6 +420,13 @@ public class ResolvableTypeTests {
assertThat(type.getGeneric().getGeneric().getType(), equalTo((Type) String.class));
}
@Test
public void genericOfGenericWithAs() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList")).asCollection();
assertThat(type.toString(), equalTo("java.util.Collection<java.util.List<java.lang.String>>"));
assertThat(type.getGeneric().asCollection().toString(), equalTo("java.util.Collection<java.lang.String>"));
}
@Test
public void getGenericOfGenericByIndexes() throws Exception {
ResolvableType type = ResolvableType.forField(Fields.class.getField("stringListList"));
@ -437,13 +449,21 @@ public class ResolvableTypeTests {
}
@Test
public void getGenerics() throws Exception {
public void getGenericsFromParameterizedType() throws Exception {
ResolvableType type = ResolvableType.forClass(List.class, ExtendsList.class);
ResolvableType[] generics = type.getGenerics();
assertThat(generics.length, equalTo(1));
assertThat(generics[0].resolve(), equalTo((Class) CharSequence.class));
}
@Test
public void getGenericsFromClass() throws Exception {
ResolvableType type = ResolvableType.forClass(List.class);
ResolvableType[] generics = type.getGenerics();
assertThat(generics.length, equalTo(1));
assertThat(generics[0].getType().toString(), equalTo("E"));
}
@Test
public void noGetGenerics() throws Exception {
ResolvableType type = ResolvableType.forClass(ExtendsList.class);
@ -549,9 +569,8 @@ public class ResolvableTypeTests {
public void doesResolveFromOuterOwner() throws Exception {
ResolvableType type = ResolvableType.forField(
Fields.class.getField("listOfListOfUnknown")).as(Collection.class);
ResolvableType generic = type.getGeneric(0);
assertThat(generic.resolve(), equalTo((Class) List.class));
assertThat(generic.as(Collection.class).getGeneric(0).as(Collection.class).resolve(), nullValue());
assertThat(type.getGeneric(0).resolve(), equalTo((Class) List.class));
assertThat(type.getGeneric(0).as(Collection.class).getGeneric(0).as(Collection.class).resolve(), nullValue());
}
@Test
@ -721,15 +740,16 @@ public class ResolvableTypeTests {
public void resolveTypeVariableFromTypeWithVariableResolver() throws Exception {
Type sourceType = Methods.class.getMethod("typedReturn").getGenericReturnType();
ResolvableType type = ResolvableType.forType(
sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class));
sourceType, ResolvableType.forClass(TypedMethods.class).as(Methods.class).asVariableResolver());
assertThat(type.resolve(), equalTo((Class) String.class));
assertThat(type.getType().toString(), equalTo("T"));
}
@Test
public void resolveTypeWithCustomVariableResolver() throws Exception {
TypeVariableResolver variableResolver = mock(TypeVariableResolver.class);
given(variableResolver.resolveVariable((TypeVariable<?>) anyObject())).willReturn(Long.class);
VariableResolver variableResolver = mock(VariableResolver.class);
ResolvableType longType = ResolvableType.forClass(Long.class);
given(variableResolver.resolveVariable((TypeVariable<?>) anyObject())).willReturn(longType);
ResolvableType variable = ResolvableType.forType(
Fields.class.getField("typeVariableType").getGenericType(), variableResolver);
@ -747,10 +767,10 @@ public class ResolvableTypeTests {
public void toStrings() throws Exception {
assertThat(ResolvableType.NONE.toString(), equalTo("?"));
assertFieldToStringValue("classType", "java.util.List");
assertFieldToStringValue("classType", "java.util.List<?>");
assertFieldToStringValue("typeVariableType", "?");
assertFieldToStringValue("parameterizedType", "java.util.List<?>");
assertFieldToStringValue("arrayClassType", "java.util.List[]");
assertFieldToStringValue("arrayClassType", "java.util.List<?>[]");
assertFieldToStringValue("genericArrayType", "java.util.List<java.lang.String>[]");
assertFieldToStringValue("genericMultiArrayType", "java.util.List<java.lang.String>[][][]");
assertFieldToStringValue("wildcardType", "java.util.List<java.lang.Number>");
@ -761,7 +781,7 @@ public class ResolvableTypeTests {
assertFieldToStringValue("stringArrayList", "java.util.List<java.lang.String[]>");
assertFieldToStringValue("stringIntegerMultiValueMap", "org.springframework.util.MultiValueMap<java.lang.String, java.lang.Integer>");
assertFieldToStringValue("stringIntegerMultiValueMapSwitched", VariableNameSwitch.class.getName() + "<java.lang.Integer, java.lang.String>");
assertFieldToStringValue("listOfListOfUnknown", "java.util.List<java.util.List>");
assertFieldToStringValue("listOfListOfUnknown", "java.util.List<java.util.List<?>>");
assertTypedFieldToStringValue("typeVariableType", "java.lang.String");
assertTypedFieldToStringValue("parameterizedType", "java.util.List<java.lang.String>");
@ -789,6 +809,17 @@ public class ResolvableTypeTests {
assertThat(type.resolve(), equalTo((Type) Integer.class));
}
@Test
public void resolveFromClassWithGenerics() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(List.class, ResolvableType.forClassWithGenerics(List.class, String.class));
assertThat(type.asCollection().toString(), equalTo("java.util.Collection<java.util.List<java.lang.String>>"));
assertThat(type.asCollection().getGeneric().toString(), equalTo("java.util.List<java.lang.String>"));
assertThat(type.asCollection().getGeneric().asCollection().toString(), equalTo("java.util.Collection<java.lang.String>"));
assertThat(type.toString(), equalTo("java.util.List<java.util.List<java.lang.String>>"));
assertThat(type.asCollection().getGeneric().getGeneric().resolve(), equalTo((Type) String.class));
}
@Test
public void isAssignableFromMustNotBeNull() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
@ -997,7 +1028,7 @@ public class ResolvableTypeTests {
public void hashCodeAndEquals() throws Exception {
ResolvableType forClass = ResolvableType.forClass(List.class);
ResolvableType forFieldDirect = ResolvableType.forField(Fields.class.getDeclaredField("stringList"));
ResolvableType forFieldViaType = ResolvableType.forType(Fields.class.getDeclaredField("stringList").getGenericType());
ResolvableType forFieldViaType = ResolvableType.forType(Fields.class.getDeclaredField("stringList").getGenericType(), (VariableResolver) null);
ResolvableType forFieldWithImplementation = ResolvableType.forField(Fields.class.getDeclaredField("stringList"), TypedFields.class);
assertThat(forClass, equalTo(forClass));
@ -1024,6 +1055,60 @@ public class ResolvableTypeTests {
assertThat(t.resolveGeneric(1, 0), equalTo((Class) String.class));
}
@Test
public void forClassWithGenerics() throws Exception {
ResolvableType elementType = ResolvableType.forClassWithGenerics(Map.class, Integer.class, String.class);
ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType);
assertThat(listType.toString(), equalTo("java.util.List<java.util.Map<java.lang.Integer, java.lang.String>>"));
}
@Test
public void classWithGenericsAs() throws Exception {
ResolvableType type = ResolvableType.forClassWithGenerics(MultiValueMap.class, Integer.class, String.class);
assertThat(type.asMap().toString(), equalTo("java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>"));
}
@Test
public void forClassWithMismatchedGenerics() throws Exception {
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Missmatched number of generics specified");
ResolvableType.forClassWithGenerics(Map.class, Integer.class);
}
@Test
public void forArrayComponent() throws Exception {
ResolvableType elementType = ResolvableType.forField(Fields.class.getField("stringList"));
ResolvableType type = ResolvableType.forArrayComponent(elementType);
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
assertThat(type.resolve(), equalTo((Class) List[].class));
}
@Test
public void serialize() throws Exception {
testSerialization(ResolvableType.forClass(List.class));
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")));
testSerialization(ResolvableType.forMethodParameter(Methods.class.getMethod("charSequenceParameter", List.class), 0));
testSerialization(ResolvableType.forMethodReturnType(Methods.class.getMethod("charSequenceReturn")));
testSerialization(ResolvableType.forConstructorParameter(Constructors.class.getConstructor(List.class), 0));
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")).getGeneric());
testSerialization(ResolvableType.forField(Fields.class.getField("charSequenceList")).asCollection());
testSerialization(ResolvableType.forClass(ExtendsMap.class).getSuperType());
ResolvableType deserializedNone = testSerialization(ResolvableType.NONE);
assertThat(deserializedNone, sameInstance(ResolvableType.NONE));
}
private ResolvableType testSerialization(ResolvableType type) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(type);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ResolvableType read = (ResolvableType) ois.readObject();
assertThat(read, equalTo(type));
assertThat(read.getType(), equalTo(type.getType()));
assertThat(read.resolve(), equalTo((Class) type.resolve()));
return read;
}
private static AssertAssignbleMatcher assertAssignable(final ResolvableType type,
final ResolvableType... fromTypes) {

View File

@ -0,0 +1,171 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Tests for {@link SerializableTypeWrapper}.
*
* @author Phillip Webb
*/
public class SerializableTypeWrapperTests {
@Test
public void forField() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
}
@Test
public void forMethodParameter() throws Exception {
Method method = Methods.class.getDeclaredMethod("method", Class.class, Object.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(method, 0));
assertThat(type.toString(), equalTo("java.lang.Class<T>"));
assertSerialzable(type);
}
@Test
public void forConstructor() throws Exception {
Constructor<?> constructor = Constructors.class.getDeclaredConstructor(List.class);
Type type = SerializableTypeWrapper.forMethodParameter(MethodParameter.forMethodOrConstructor(constructor, 0));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
}
@Test
public void forGenericSuperClass() throws Exception {
Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class);
assertThat(type.toString(), equalTo("java.util.AbstractList<E>"));
assertSerialzable(type);
}
@Test
public void forGenericInterfaces() throws Exception {
Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0];
assertThat(type.toString(), equalTo("java.util.Collection<E>"));
assertSerialzable(type);
}
@Test
public void forTypeParamters() throws Exception {
Type type = SerializableTypeWrapper.forTypeParameters(List.class)[0];
assertThat(type.toString(), equalTo("E"));
assertSerialzable(type);
}
@Test
public void classType() throws Exception {
Type type = SerializableTypeWrapper.forField(Fields.class.getField("classType"));
assertThat(type.toString(), equalTo("class java.lang.String"));
assertSerialzable(type);
}
@Test
public void genericArrayType() throws Exception {
GenericArrayType type = (GenericArrayType) SerializableTypeWrapper.forField(Fields.class.getField("genericArrayType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>[]"));
assertSerialzable(type);
assertSerialzable(type.getGenericComponentType());
}
@Test
public void parameterizedType() throws Exception {
ParameterizedType type = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("parameterizedType"));
assertThat(type.toString(), equalTo("java.util.List<java.lang.String>"));
assertSerialzable(type);
assertSerialzable(type.getOwnerType());
assertSerialzable(type.getRawType());
assertSerialzable(type.getActualTypeArguments());
assertSerialzable(type.getActualTypeArguments()[0]);
}
@Test
public void typeVariableType() throws Exception {
TypeVariable<?> type = (TypeVariable<?>) SerializableTypeWrapper.forField(Fields.class.getField("typeVariableType"));
assertThat(type.toString(), equalTo("T"));
assertSerialzable(type);
assertSerialzable(type.getBounds());
}
@Test
public void wildcardType() throws Exception {
ParameterizedType typeSource = (ParameterizedType) SerializableTypeWrapper.forField(Fields.class.getField("wildcardType"));
WildcardType type = (WildcardType) typeSource.getActualTypeArguments()[0];
assertThat(type.toString(), equalTo("? extends java.lang.CharSequence"));
assertSerialzable(type);
assertSerialzable(type.getLowerBounds());
assertSerialzable(type.getUpperBounds());
}
private void assertSerialzable(Object source) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(source);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
assertThat(ois.readObject(), equalTo(source));
}
static class Fields<T> {
public String classType;
public List<String>[] genericArrayType;
public List<String> parameterizedType;
public T typeVariableType;
public List<? extends CharSequence> wildcardType;
}
static interface Methods {
<T> List<T> method(Class<T> p1, T p2);
}
static class Constructors {
public Constructors(List<String> p) {
}
}
}