diff --git a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java index 8b4c280a97f..9a3e0af43db 100644 --- a/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ b/spring-core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java @@ -16,15 +16,8 @@ package org.springframework.core; -import java.lang.reflect.Array; import java.lang.reflect.Field; -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.Collection; import java.util.Map; @@ -36,7 +29,9 @@ import java.util.Map; * (to be able to attempt type conversion if appropriate). * * @author Juergen Hoeller + * @author Phillip Webb * @since 2.0 + * @see ResolvableType */ public abstract class GenericCollectionTypeResolver { @@ -47,7 +42,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionType(Class collectionClass) { - return extractTypeFromClass(collectionClass, Collection.class, 0); + return ResolvableType.forClass(collectionClass).asCollection().resolveGeneric(); } /** @@ -57,7 +52,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyType(Class mapClass) { - return extractTypeFromClass(mapClass, Map.class, 0); + return ResolvableType.forClass(mapClass).asMap().resolveGeneric(0); } /** @@ -67,7 +62,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueType(Class mapClass) { - return extractTypeFromClass(mapClass, Map.class, 1); + return ResolvableType.forClass(mapClass).asMap().resolveGeneric(1); } /** @@ -76,7 +71,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionFieldType(Field collectionField) { - return getGenericFieldType(collectionField, Collection.class, 0, null, 1); + return ResolvableType.forField(collectionField).asCollection().resolveGeneric(); } /** @@ -88,7 +83,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionFieldType(Field collectionField, int nestingLevel) { - return getGenericFieldType(collectionField, Collection.class, 0, null, nestingLevel); + return ResolvableType.forField(collectionField).getNested(nestingLevel).asCollection().resolveGeneric(); } /** @@ -100,9 +95,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class getCollectionFieldType(Field collectionField, int nestingLevel, Map typeIndexesPerLevel) { - return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(collectionField).getNested(nestingLevel, typeIndexesPerLevel).asCollection().resolveGeneric(); } /** @@ -111,7 +108,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 0, null, 1); + return ResolvableType.forField(mapField).asMap().resolveGeneric(0); } /** @@ -123,7 +120,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 0, null, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(0); } /** @@ -135,9 +132,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class getMapKeyFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { - return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(0); } /** @@ -146,7 +145,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 1, null, 1); + return ResolvableType.forField(mapField).asMap().resolveGeneric(1); } /** @@ -158,7 +157,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 1, null, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel).asMap().resolveGeneric(1); } /** @@ -170,9 +169,11 @@ public abstract class GenericCollectionTypeResolver { * @param typeIndexesPerLevel Map keyed by nesting level, with each value * expressing the type index for traversal at that level * @return the generic type, or {@code null} if none + * @deprecated as of 4.0 in favor of using {@link ResolvableType} for arbitrary nesting levels */ + @Deprecated public static Class getMapValueFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { - return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel); + return ResolvableType.forField(mapField).getNested(nestingLevel, typeIndexesPerLevel).asMap().resolveGeneric(1); } /** @@ -181,7 +182,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Collection.class, 0); + return forMethodParameter(methodParam).asCollection().resolveGeneric(); } /** @@ -190,7 +191,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Map.class, 0); + return forMethodParameter(methodParam).asMap().resolveGeneric(0); } /** @@ -199,7 +200,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueParameterType(MethodParameter methodParam) { - return getGenericParameterType(methodParam, Map.class, 1); + return forMethodParameter(methodParam).asMap().resolveGeneric(1); } /** @@ -208,7 +209,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionReturnType(Method method) { - return getGenericReturnType(method, Collection.class, 0, 1); + return ResolvableType.forMethodReturn(method).asCollection().resolveGeneric(); } /** @@ -222,7 +223,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getCollectionReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Collection.class, 0, nestingLevel); + return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asCollection().resolveGeneric(); } /** @@ -231,7 +232,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyReturnType(Method method) { - return getGenericReturnType(method, Map.class, 0, 1); + return ResolvableType.forMethodReturn(method).asMap().resolveGeneric(0); } /** @@ -243,7 +244,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapKeyReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Map.class, 0, nestingLevel); + return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asMap().resolveGeneric(0); } /** @@ -252,7 +253,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueReturnType(Method method) { - return getGenericReturnType(method, Map.class, 1, 1); + return ResolvableType.forMethodReturn(method).asMap().resolveGeneric(1); } /** @@ -264,220 +265,14 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or {@code null} if none */ public static Class getMapValueReturnType(Method method, int nestingLevel) { - return getGenericReturnType(method, Map.class, 1, nestingLevel); + return ResolvableType.forMethodReturn(method).getNested(nestingLevel).asMap().resolveGeneric(1); } - - /** - * Extract the generic parameter type from the given method or constructor. - * @param methodParam the method parameter specification - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @return the generic type, or {@code null} if none - */ - private static Class getGenericParameterType(MethodParameter methodParam, Class source, int typeIndex) { - return extractType(GenericTypeResolver.getTargetType(methodParam), source, typeIndex, - methodParam.typeVariableMap, methodParam.typeIndexesPerLevel, methodParam.getNestingLevel(), 1); - } - - /** - * Extract the generic type from the given field. - * @param field the field to check the type for - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @param nestingLevel the nesting level of the target type - * @return the generic type, or {@code null} if none - */ - private static Class getGenericFieldType(Field field, Class source, int typeIndex, - Map typeIndexesPerLevel, int nestingLevel) { - return extractType(field.getGenericType(), source, typeIndex, null, typeIndexesPerLevel, nestingLevel, 1); - } - - /** - * Extract the generic return type from the given method. - * @param method the method to check the return type for - * @param source the source class/interface defining the generic parameter types - * @param typeIndex the index of the type (e.g. 0 for Collections, - * 0 for Map keys, 1 for Map values) - * @param nestingLevel the nesting level of the target type - * @return the generic type, or {@code null} if none - */ - private static Class getGenericReturnType(Method method, Class source, int typeIndex, int nestingLevel) { - return extractType(method.getGenericReturnType(), source, typeIndex, null, null, nestingLevel, 1); - } - - /** - * Extract the generic type from the given Type object. - * @param type the Type to check - * @param source the source collection/map Class that we check - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class extractType(Type type, Class source, int typeIndex, - Map typeVariableMap, Map typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - Type resolvedType = type; - if (type instanceof TypeVariable && typeVariableMap != null) { - Type mappedType = typeVariableMap.get(type); - if (mappedType != null) { - resolvedType = mappedType; - } + private static ResolvableType forMethodParameter(MethodParameter methodParam) { + if (methodParam.resolveClass != null) { + return ResolvableType.forMethodParameter(methodParam, methodParam.resolveClass); } - if (resolvedType instanceof ParameterizedType) { - return extractTypeFromParameterizedType((ParameterizedType) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - } - else if (resolvedType instanceof Class) { - return extractTypeFromClass((Class) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - } - else if (resolvedType instanceof GenericArrayType) { - Type compType = ((GenericArrayType) resolvedType).getGenericComponentType(); - return extractType(compType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel + 1); - } - else { - return null; - } - } - - /** - * Extract the generic type from the given ParameterizedType object. - * @param ptype the ParameterizedType to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class extractTypeFromParameterizedType(ParameterizedType ptype, Class source, int typeIndex, - Map typeVariableMap, Map typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - if (!(ptype.getRawType() instanceof Class)) { - return null; - } - Class rawType = (Class) ptype.getRawType(); - Type[] paramTypes = ptype.getActualTypeArguments(); - if (nestingLevel - currentLevel > 0) { - int nextLevel = currentLevel + 1; - Integer currentTypeIndex = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(nextLevel) : null); - // Default is last parameter type: Collection element or Map value. - int indexToUse = (currentTypeIndex != null ? currentTypeIndex : paramTypes.length - 1); - Type paramType = paramTypes[indexToUse]; - return extractType(paramType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, nextLevel); - } - if (source != null && !source.isAssignableFrom(rawType)) { - return null; - } - Class fromSuperclassOrInterface = extractTypeFromClass(rawType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, - nestingLevel, currentLevel); - if (fromSuperclassOrInterface != null) { - return fromSuperclassOrInterface; - } - if (paramTypes == null || typeIndex >= paramTypes.length) { - return null; - } - Type paramType = paramTypes[typeIndex]; - if (paramType instanceof TypeVariable && typeVariableMap != null) { - Type mappedType = typeVariableMap.get(paramType); - if (mappedType != null) { - paramType = mappedType; - } - } - if (paramType instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) paramType; - Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds != null && upperBounds.length > 0 && !Object.class.equals(upperBounds[0])) { - paramType = upperBounds[0]; - } - else { - Type[] lowerBounds = wildcardType.getLowerBounds(); - if (lowerBounds != null && lowerBounds.length > 0 && !Object.class.equals(lowerBounds[0])) { - paramType = lowerBounds[0]; - } - } - } - if (paramType instanceof ParameterizedType) { - paramType = ((ParameterizedType) paramType).getRawType(); - } - if (paramType instanceof GenericArrayType) { - // A generic array type... Let's turn it into a straight array type if possible. - Type compType = ((GenericArrayType) paramType).getGenericComponentType(); - if (compType instanceof Class) { - return Array.newInstance((Class) compType, 0).getClass(); - } - } - else if (paramType instanceof Class) { - // We finally got a straight Class... - return (Class) paramType; - } - return null; - } - - /** - * Extract the generic type from the given Class object. - * @param clazz the Class to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @return the generic type as Class, or {@code null} if none - */ - private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex) { - return extractTypeFromClass(clazz, source, typeIndex, null, null, 1, 1); - } - - /** - * Extract the generic type from the given Class object. - * @param clazz the Class to check - * @param source the expected raw source type (can be {@code null}) - * @param typeIndex the index of the actual type argument - * @param nestingLevel the nesting level of the target type - * @param currentLevel the current nested level - * @return the generic type as Class, or {@code null} if none - */ - private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex, - Map typeVariableMap, Map typeIndexesPerLevel, - int nestingLevel, int currentLevel) { - - if (clazz.getName().startsWith("java.util.")) { - return null; - } - if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) { - try { - return extractType(clazz.getGenericSuperclass(), source, typeIndex, typeVariableMap, - typeIndexesPerLevel, nestingLevel, currentLevel); - } - catch (MalformedParameterizedTypeException ex) { - // from getGenericSuperclass() - ignore and continue with interface introspection - } - } - Type[] ifcs = clazz.getGenericInterfaces(); - if (ifcs != null) { - for (Type ifc : ifcs) { - Type rawType = ifc; - if (ifc instanceof ParameterizedType) { - rawType = ((ParameterizedType) ifc).getRawType(); - } - if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) { - return extractType(ifc, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel); - } - } - } - return null; - } - - /** - * Determine whether the given class is a potential candidate - * that defines generic collection or map types. - * @param clazz the class to check - * @return whether the given class is assignable to Collection or Map - */ - private static boolean isIntrospectionCandidate(Class clazz) { - return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz)); + return ResolvableType.forMethodParameter(methodParam); } }