diff --git a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java index 865ac36036..5bcfe63c9b 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -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 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 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 typeVariableMap; - - public TypeVariableMapResolver(Map 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(); - } - } - } 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 840eafe7a6..d2486b393f 100644 --- a/spring-core/src/main/java/org/springframework/core/ResolvableType.java +++ b/spring-core/src/main/java/org/springframework/core/ResolvableType.java @@ -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; * *

{@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: *

  * private HashMap<Integer, List<String>> 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 cache =
 			new ConcurrentReferenceHashMap();
@@ -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;
+				}
 			}
+
+			// Fallback to bounds
+			return forType(resolveBounds(variable.getBounds()), this.variableResolver);
 		}
-		return (resolved == null ? NONE : forType(resolved, 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 (this.variableResolver != null) {
-				resolved = this.variableResolver.resolveVariable(variable);
+
+			if (parameterizedType.getOwnerType() != null) {
+				return forType(parameterizedType.getOwnerType(),
+						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);
+
+		if (this.variableResolver != null) {
+			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 {
 	 * 

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); } diff --git a/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java new file mode 100644 index 0000000000..d20c8c77af --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/SerializableTypeWrapper.java @@ -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. + * + *

{@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. + * + *

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()); + } + + } +} diff --git a/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java b/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java deleted file mode 100644 index fba6b62ad9..0000000000 --- a/spring-core/src/main/java/org/springframework/core/TypeVariableResolver.java +++ /dev/null @@ -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); - -} 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 b33efa9c3b..3d0aa09af8 100644 --- a/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java +++ b/spring-core/src/test/java/org/springframework/core/ResolvableTypeTests.java @@ -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>")); + assertThat(type.getGeneric().asCollection().toString(), equalTo("java.util.Collection")); + } + @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[]"); assertFieldToStringValue("genericMultiArrayType", "java.util.List[][][]"); assertFieldToStringValue("wildcardType", "java.util.List"); @@ -761,7 +781,7 @@ public class ResolvableTypeTests { assertFieldToStringValue("stringArrayList", "java.util.List"); assertFieldToStringValue("stringIntegerMultiValueMap", "org.springframework.util.MultiValueMap"); assertFieldToStringValue("stringIntegerMultiValueMapSwitched", VariableNameSwitch.class.getName() + ""); - assertFieldToStringValue("listOfListOfUnknown", "java.util.List"); + assertFieldToStringValue("listOfListOfUnknown", "java.util.List>"); assertTypedFieldToStringValue("typeVariableType", "java.lang.String"); assertTypedFieldToStringValue("parameterizedType", "java.util.List"); @@ -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>")); + assertThat(type.asCollection().getGeneric().toString(), equalTo("java.util.List")); + assertThat(type.asCollection().getGeneric().asCollection().toString(), equalTo("java.util.Collection")); + assertThat(type.toString(), equalTo("java.util.List>")); + 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>")); + } + + @Test + public void classWithGenericsAs() throws Exception { + ResolvableType type = ResolvableType.forClassWithGenerics(MultiValueMap.class, Integer.class, String.class); + assertThat(type.asMap().toString(), equalTo("java.util.Map>")); + } + + @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[]")); + 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) { diff --git a/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java new file mode 100644 index 0000000000..530cc5e8a1 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/SerializableTypeWrapperTests.java @@ -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")); + 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")); + 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")); + assertSerialzable(type); + } + + @Test + public void forGenericSuperClass() throws Exception { + Type type = SerializableTypeWrapper.forGenericSuperclass(ArrayList.class); + assertThat(type.toString(), equalTo("java.util.AbstractList")); + assertSerialzable(type); + } + + @Test + public void forGenericInterfaces() throws Exception { + Type type = SerializableTypeWrapper.forGenericInterfaces(List.class)[0]; + assertThat(type.toString(), equalTo("java.util.Collection")); + 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[]")); + 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")); + 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 { + + public String classType; + + public List[] genericArrayType; + + public List parameterizedType; + + public T typeVariableType; + + public List wildcardType; + + } + + static interface Methods { + + List method(Class p1, T p2); + + } + + static class Constructors { + + public Constructors(List p) { + } + + } +}