From 595efe9aab884bab233c61278f00ef325446d268 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 9 Oct 2013 18:49:35 -0700 Subject: [PATCH] Use ResolvableType in GenericTypeResolver Refactor GenericTypeResolver to make use of ResolvableType for generic resolution. Issue: SPR-10978 --- .../core/GenericTypeResolver.java | 347 ++++++------------ .../springframework/core/MethodParameter.java | 4 +- .../core/GenericTypeResolverTests.java | 48 +++ 3 files changed, 158 insertions(+), 241 deletions(-) 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 a63796f0eab..539eb751a8f 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericTypeResolver.java @@ -16,14 +16,12 @@ package org.springframework.core; -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.MalformedParameterizedTypeException; 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.Collections; import java.util.HashMap; import java.util.Map; @@ -39,6 +37,7 @@ import org.springframework.util.ConcurrentReferenceHashMap; * @author Juergen Hoeller * @author Rob Harrop * @author Sam Brannen + * @author Phillip Webb * @since 2.5.2 * @see GenericCollectionTypeResolver */ @@ -53,20 +52,12 @@ public abstract class GenericTypeResolver { * Determine the target type for the given parameter specification. * @param methodParam the method parameter specification * @return the corresponding generic parameter type + * @deprecated as of Spring 4.0 use {@link MethodParameter#getGenericParameterType()} */ + @Deprecated public static Type getTargetType(MethodParameter methodParam) { Assert.notNull(methodParam, "MethodParameter must not be null"); - if (methodParam.getConstructor() != null) { - return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()]; - } - else { - if (methodParam.getParameterIndex() >= 0) { - return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()]; - } - else { - return methodParam.getMethod().getGenericReturnType(); - } - } + return methodParam.getGenericParameterType(); } /** @@ -76,14 +67,11 @@ public abstract class GenericTypeResolver { * @return the corresponding generic parameter or return type */ public static Class resolveParameterType(MethodParameter methodParam, Class clazz) { - Type genericType = getTargetType(methodParam); + Assert.notNull(methodParam, "MethodParameter must not be null"); Assert.notNull(clazz, "Class must not be null"); - Map typeVariableMap = getTypeVariableMap(clazz); - Type rawType = getRawType(genericType, typeVariableMap); - Class result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType()); - methodParam.setParameterType(result); - methodParam.typeVariableMap = typeVariableMap; - return result; + methodParam.resolveClass = clazz; + methodParam.setParameterType(ResolvableType.forMethodParameter(methodParam, clazz).resolve()); + return methodParam.getParameterType(); } /** @@ -96,11 +84,8 @@ public abstract class GenericTypeResolver { */ public static Class resolveReturnType(Method method, Class clazz) { Assert.notNull(method, "Method must not be null"); - Type genericType = method.getGenericReturnType(); Assert.notNull(clazz, "Class must not be null"); - Map typeVariableMap = getTypeVariableMap(clazz); - Type rawType = getRawType(genericType, typeVariableMap); - return (rawType instanceof Class ? (Class) rawType : method.getReturnType()); + return ResolvableType.forMethodReturn(method, clazz).resolve(method.getReturnType()); } /** @@ -202,22 +187,11 @@ public abstract class GenericTypeResolver { */ public static Class resolveReturnTypeArgument(Method method, Class genericIfc) { Assert.notNull(method, "method must not be null"); - Type returnType = method.getReturnType(); - Type genericReturnType = method.getGenericReturnType(); - if (returnType.equals(genericIfc)) { - if (genericReturnType instanceof ParameterizedType) { - ParameterizedType targetType = (ParameterizedType) genericReturnType; - Type[] actualTypeArguments = targetType.getActualTypeArguments(); - Type typeArg = actualTypeArguments[0]; - if (!(typeArg instanceof WildcardType)) { - return (Class) typeArg; - } - } - else { - return null; - } + ResolvableType resolvableType = ResolvableType.forMethodReturn(method).as(genericIfc); + if (!resolvableType.hasGenerics() || resolvableType.getType() instanceof WildcardType) { + return null; } - return resolveTypeArgument((Class) returnType, genericIfc); + return getSingleGeneric(resolvableType); } /** @@ -229,17 +203,22 @@ public abstract class GenericTypeResolver { * @return the resolved type of the argument, or {@code null} if not resolvable */ public static Class resolveTypeArgument(Class clazz, Class genericIfc) { - Class[] typeArgs = resolveTypeArguments(clazz, genericIfc); - if (typeArgs == null) { + ResolvableType resolvableType = ResolvableType.forClass(clazz).as(genericIfc); + if (!resolvableType.hasGenerics()) { return null; } - if (typeArgs.length != 1) { + return getSingleGeneric(resolvableType); + } + + private static Class getSingleGeneric(ResolvableType resolvableType) { + if (resolvableType.getGenerics().length > 1) { throw new IllegalArgumentException("Expected 1 type argument on generic interface [" + - genericIfc.getName() + "] but found " + typeArgs.length); + resolvableType + "] but found " + resolvableType.getGenerics().length); } - return typeArgs[0]; + return resolvableType.getGeneric().resolve(); } + /** * Resolve the type arguments of the given generic interface against the given * target class which is assumed to implement the generic interface and possibly @@ -250,103 +229,68 @@ public abstract class GenericTypeResolver { * number of actual type arguments, or {@code null} if not resolvable */ public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) { - return doResolveTypeArguments(clazz, clazz, genericIfc); - } - - private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) { - while (classToIntrospect != null) { - if (genericIfc.isInterface()) { - Type[] ifcs = classToIntrospect.getGenericInterfaces(); - for (Type ifc : ifcs) { - Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc); - if (result != null) { - return result; - } - } - } - else { - try { - Class[] result = doResolveTypeArguments(ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc); - if (result != null) { - return result; - } - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - return null to skip further superclass traversal - return null; - } - } - classToIntrospect = classToIntrospect.getSuperclass(); + ResolvableType type = ResolvableType.forClass(clazz).as(genericIfc); + if(!type.hasGenerics()) { + return null; } - return null; + return type.resolveGenerics(); } - private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) { - if (ifc instanceof ParameterizedType) { - ParameterizedType paramIfc = (ParameterizedType) ifc; - Type rawType = paramIfc.getRawType(); - if (genericIfc.equals(rawType)) { - Type[] typeArgs = paramIfc.getActualTypeArguments(); - Class[] result = new Class[typeArgs.length]; - for (int i = 0; i < typeArgs.length; i++) { - Type arg = typeArgs[i]; - result[i] = extractClass(ownerClass, arg); - } - return result; - } - else if (genericIfc.isAssignableFrom((Class) rawType)) { - return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc); - } - } - else if (ifc != null && genericIfc.isAssignableFrom((Class) ifc)) { - return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc); - } - return null; + /** + * Resolve the specified generic type against the given TypeVariable map. + * @param genericType the generic type to resolve + * @param typeVariableMap the TypeVariable Map to resolved against + * @return the type if it resolves to a Class, or {@code Object.class} otherwise + * @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); } /** - * Extract a Class from the given Type. + * Build a mapping of {@link TypeVariable#getName TypeVariable names} to + * {@link Class concrete classes} for the specified {@link Class}. Searches + * all super types, enclosing types and interfaces. + * @deprecated as of Spring 4.0 in favor of {@link ResolvableType} */ - private static Class extractClass(Class ownerClass, Type arg) { - if (arg instanceof ParameterizedType) { - return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); - } - else if (arg instanceof GenericArrayType) { - GenericArrayType gat = (GenericArrayType) arg; - Type gt = gat.getGenericComponentType(); - Class componentClass = extractClass(ownerClass, gt); - return Array.newInstance(componentClass, 0).getClass(); + @Deprecated + public static Map getTypeVariableMap(Class clazz) { + Map typeVariableMap = typeVariableCache.get(clazz); + if (typeVariableMap == null) { + typeVariableMap = new HashMap(); + buildTypeVaraibleMap(ResolvableType.forClass(clazz), typeVariableMap); + typeVariableCache.put(clazz, Collections.unmodifiableMap(typeVariableMap)); } - else if (arg instanceof TypeVariable) { - TypeVariable tv = (TypeVariable) arg; - arg = getTypeVariableMap(ownerClass).get(tv); - if (arg == null) { - arg = extractBoundForTypeVariable(tv); - if (arg instanceof ParameterizedType) { - return extractClass(ownerClass, ((ParameterizedType) arg).getRawType()); + return typeVariableMap; + } + + private static void buildTypeVaraibleMap(ResolvableType type, + Map typeVariableMap) { + if(type != ResolvableType.NONE) { + if(type.getType() instanceof ParameterizedType) { + TypeVariable[] variables = type.resolve().getTypeParameters(); + for (int i = 0; i < variables.length; i++) { + ResolvableType generic = type.getGeneric(i); + while (generic.getType() instanceof TypeVariable) { + generic = generic.resolveType(); + } + if (generic != ResolvableType.NONE) { + typeVariableMap.put(variables[i], generic.getType()); + } } } - else { - return extractClass(ownerClass, arg); + buildTypeVaraibleMap(type.getSuperType(), typeVariableMap); + for (ResolvableType interfaceType : type.getInterfaces()) { + buildTypeVaraibleMap(interfaceType, typeVariableMap); + } + if (type.resolve().isMemberClass()) { + buildTypeVaraibleMap(ResolvableType.forClass(type.resolve().getEnclosingClass()), + typeVariableMap); } } - return (arg instanceof Class ? (Class) arg : Object.class); - } - - /** - * Resolve the specified generic type against the given TypeVariable map. - * @param genericType the generic type to resolve - * @param typeVariableMap the TypeVariable Map to resolved against - * @return the type if it resolves to a Class, or {@code Object.class} otherwise - */ - public static Class resolveType(Type genericType, Map typeVariableMap) { - Type resolvedType = getRawType(genericType, typeVariableMap); - if (resolvedType instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) resolvedType).getGenericComponentType(); - Class componentClass = resolveType(componentType, typeVariableMap); - resolvedType = Array.newInstance(componentClass, 0).getClass(); - } - return (resolvedType instanceof Class ? (Class) resolvedType : Object.class); } /** @@ -355,6 +299,7 @@ public abstract class GenericTypeResolver { * @param typeVariableMap the TypeVariable Map to resolved against * @return the resolved raw type */ + @Deprecated static Type getRawType(Type genericType, Map typeVariableMap) { Type resolvedType = genericType; if (genericType instanceof TypeVariable) { @@ -372,62 +317,10 @@ public abstract class GenericTypeResolver { } } - /** - * Build a mapping of {@link TypeVariable#getName TypeVariable names} to - * {@link Class concrete classes} for the specified {@link Class}. Searches - * all super types, enclosing types and interfaces. - */ - public static Map getTypeVariableMap(Class clazz) { - Map typeVariableMap = typeVariableCache.get(clazz); - - if (typeVariableMap == null) { - typeVariableMap = new HashMap(); - - // interfaces - extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap); - - try { - // super class - Class type = clazz; - while (type.getSuperclass() != null && !Object.class.equals(type.getSuperclass())) { - Type genericType = type.getGenericSuperclass(); - if (genericType instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericType; - populateTypeMapFromParameterizedType(pt, typeVariableMap); - } - extractTypeVariablesFromGenericInterfaces(type.getSuperclass().getGenericInterfaces(), typeVariableMap); - type = type.getSuperclass(); - } - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and continue with member class check - } - - try { - // enclosing class - Class type = clazz; - while (type.isMemberClass()) { - Type genericType = type.getGenericSuperclass(); - if (genericType instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericType; - populateTypeMapFromParameterizedType(pt, typeVariableMap); - } - type = type.getEnclosingClass(); - } - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and preserve previously accumulated type variables - } - - typeVariableCache.put(clazz, typeVariableMap); - } - - return typeVariableMap; - } - /** * Extracts the bound {@code Type} for a given {@link TypeVariable}. */ + @Deprecated static Type extractBoundForTypeVariable(TypeVariable typeVariable) { Type[] bounds = typeVariable.getBounds(); if (bounds.length == 0) { @@ -440,67 +333,43 @@ public abstract class GenericTypeResolver { return bound; } - private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map typeVariableMap) { - for (Type genericInterface : genericInterfaces) { - if (genericInterface instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) genericInterface; - populateTypeMapFromParameterizedType(pt, typeVariableMap); - if (pt.getRawType() instanceof Class) { - extractTypeVariablesFromGenericInterfaces( - ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap); - } - } - else if (genericInterface instanceof Class) { - extractTypeVariablesFromGenericInterfaces( - ((Class) genericInterface).getGenericInterfaces(), typeVariableMap); - } - } - } /** - * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType} - * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} -> - * concrete type to the supplied {@link Map}. - *

Consider this case: - *