diff --git a/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java index e4d4ae33fb4..2e2c4b1d62d 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java +++ b/org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -75,7 +75,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getCollectionFieldType(Field collectionField) { - return getGenericFieldType(collectionField, Collection.class, 0, 1); + return getGenericFieldType(collectionField, Collection.class, 0, null, 1); } /** @@ -87,7 +87,21 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getCollectionFieldType(Field collectionField, int nestingLevel) { - return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel); + return getGenericFieldType(collectionField, Collection.class, 0, null, nestingLevel); + } + + /** + * Determine the generic element type of the given Collection field. + * @param collectionField the collection field to introspect + * @param nestingLevel the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List) + * @param typeIndexesPerLevel Map keyed by nesting level, with each value + * expressing the type index for traversal at that level + * @return the generic type, or null if none + */ + public static Class getCollectionFieldType(Field collectionField, int nestingLevel, Map typeIndexesPerLevel) { + return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel); } /** @@ -96,7 +110,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getMapKeyFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 0, 1); + return getGenericFieldType(mapField, Map.class, 0, null, 1); } /** @@ -108,7 +122,21 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getMapKeyFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 0, nestingLevel); + return getGenericFieldType(mapField, Map.class, 0, null, nestingLevel); + } + + /** + * Determine the generic key type of the given Map field. + * @param mapField the map field to introspect + * @param nestingLevel the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List) + * @param typeIndexesPerLevel Map keyed by nesting level, with each value + * expressing the type index for traversal at that level + * @return the generic type, or null if none + */ + public static Class getMapKeyFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { + return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel); } /** @@ -117,7 +145,7 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getMapValueFieldType(Field mapField) { - return getGenericFieldType(mapField, Map.class, 1, 1); + return getGenericFieldType(mapField, Map.class, 1, null, 1); } /** @@ -129,7 +157,21 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ public static Class getMapValueFieldType(Field mapField, int nestingLevel) { - return getGenericFieldType(mapField, Map.class, 1, nestingLevel); + return getGenericFieldType(mapField, Map.class, 1, null, nestingLevel); + } + + /** + * Determine the generic value type of the given Map field. + * @param mapField the map field to introspect + * @param nestingLevel the nesting level of the target type + * (typically 1; e.g. in case of a List of Lists, 1 would indicate the + * nested List, whereas 2 would indicate the element of the nested List) + * @param typeIndexesPerLevel Map keyed by nesting level, with each value + * expressing the type index for traversal at that level + * @return the generic type, or null if none + */ + public static Class getMapValueFieldType(Field mapField, int nestingLevel, Map typeIndexesPerLevel) { + return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel); } /** @@ -234,8 +276,8 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ private static Class getGenericParameterType(MethodParameter methodParam, Class source, int typeIndex) { - return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam), - source, typeIndex, methodParam.getNestingLevel(), 1); + return extractType(GenericTypeResolver.getTargetType(methodParam), source, typeIndex, + methodParam.typeVariableMap, methodParam.typeIndexesPerLevel, methodParam.getNestingLevel(), 1); } /** @@ -247,8 +289,9 @@ public abstract class GenericCollectionTypeResolver { * @param nestingLevel the nesting level of the target type * @return the generic type, or null if none */ - private static Class getGenericFieldType(Field field, Class source, int typeIndex, int nestingLevel) { - return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1); + private static Class getGenericFieldType(Field field, Class source, int typeIndex, + Map typeIndexesPerLevel, int nestingLevel) { + return extractType(field.getGenericType(), source, typeIndex, null, typeIndexesPerLevel, nestingLevel, 1); } /** @@ -261,12 +304,11 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type, or null if none */ private static Class getGenericReturnType(Method method, Class source, int typeIndex, int nestingLevel) { - return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1); + return extractType(method.getGenericReturnType(), source, typeIndex, null, null, nestingLevel, 1); } /** * Extract the generic type from the given Type object. - * @param methodParam the method parameter specification * @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 @@ -274,22 +316,28 @@ public abstract class GenericCollectionTypeResolver { * @param currentLevel the current nested level * @return the generic type as Class, or null if none */ - private static Class extractType( - MethodParameter methodParam, Type type, Class source, int typeIndex, int nestingLevel, int currentLevel) { + 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 && methodParam != null && methodParam.typeVariableMap != null) { - Type mappedType = methodParam.typeVariableMap.get((TypeVariable) type); + if (type instanceof TypeVariable && typeVariableMap != null) { + Type mappedType = typeVariableMap.get((TypeVariable) type); if (mappedType != null) { resolvedType = mappedType; } } if (resolvedType instanceof ParameterizedType) { - return extractTypeFromParameterizedType( - methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel); + return extractTypeFromParameterizedType((ParameterizedType) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, + nestingLevel, currentLevel); } else if (resolvedType instanceof Class) { - return extractTypeFromClass(methodParam, (Class) resolvedType, source, typeIndex, nestingLevel, currentLevel); + 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; @@ -298,7 +346,6 @@ public abstract class GenericCollectionTypeResolver { /** * Extract the generic type from the given ParameterizedType object. - * @param methodParam the method parameter specification * @param ptype the ParameterizedType to check * @param source the expected raw source type (can be null) * @param typeIndex the index of the actual type argument @@ -306,8 +353,9 @@ public abstract class GenericCollectionTypeResolver { * @param currentLevel the current nested level * @return the generic type as Class, or null if none */ - private static Class extractTypeFromParameterizedType(MethodParameter methodParam, - ParameterizedType ptype, Class source, int typeIndex, int nestingLevel, int currentLevel) { + 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; @@ -316,17 +364,17 @@ public abstract class GenericCollectionTypeResolver { Type[] paramTypes = ptype.getActualTypeArguments(); if (nestingLevel - currentLevel > 0) { int nextLevel = currentLevel + 1; - Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null); + 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(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel); + return extractType(paramType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, nextLevel); } if (source != null && !source.isAssignableFrom(rawType)) { return null; } - Class fromSuperclassOrInterface = - extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel); + Class fromSuperclassOrInterface = extractTypeFromClass(rawType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, + nestingLevel, currentLevel); if (fromSuperclassOrInterface != null) { return fromSuperclassOrInterface; } @@ -334,8 +382,8 @@ public abstract class GenericCollectionTypeResolver { return null; } Type paramType = paramTypes[typeIndex]; - if (paramType instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) { - Type mappedType = methodParam.typeVariableMap.get((TypeVariable) paramType); + if (paramType instanceof TypeVariable && typeVariableMap != null) { + Type mappedType = typeVariableMap.get((TypeVariable) paramType); if (mappedType != null) { paramType = mappedType; } @@ -378,12 +426,11 @@ public abstract class GenericCollectionTypeResolver { * @return the generic type as Class, or null if none */ private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex) { - return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1); + return extractTypeFromClass(clazz, source, typeIndex, null, null, 1, 1); } /** * Extract the generic type from the given Class object. - * @param methodParam the method parameter specification * @param clazz the Class to check * @param source the expected raw source type (can be null) * @param typeIndex the index of the actual type argument @@ -391,14 +438,16 @@ public abstract class GenericCollectionTypeResolver { * @param currentLevel the current nested level * @return the generic type as Class, or null if none */ - private static Class extractTypeFromClass( - MethodParameter methodParam, Class clazz, Class source, int typeIndex, int nestingLevel, int currentLevel) { + 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())) { - return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel); + return extractType(clazz.getGenericSuperclass(), source, typeIndex, typeVariableMap, typeIndexesPerLevel, + nestingLevel, currentLevel); } Type[] ifcs = clazz.getGenericInterfaces(); if (ifcs != null) { @@ -408,7 +457,7 @@ public abstract class GenericCollectionTypeResolver { rawType = ((ParameterizedType) ifc).getRawType(); } if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) { - return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel); + return extractType(ifc, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel); } } } diff --git a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java index 4bb24bdac4c..361bae7eedc 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java +++ b/org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java @@ -60,12 +60,11 @@ public class MethodParameter { private int nestingLevel = 1; /** Map from Integer level to Integer type index */ - private Map typeIndexesPerLevel; + Map typeIndexesPerLevel; Map typeVariableMap; - private int hash; - + private int hash = 0; /** @@ -440,12 +439,13 @@ public class MethodParameter { @Override public int hashCode() { - int result = hash; + int result = this.hash; if (result == 0) { result = getMember().hashCode(); - result = 31 * result + parameterIndex; - hash = result; + result = 31 * result + this.parameterIndex; + this.hash = result; } return result; } + } diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java index 3a1c0cda114..023e8ca6d17 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java @@ -13,12 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Map; +/** + * @author Keith Donald + * @since 3.1 + */ abstract class AbstractDescriptor { private final Class type; @@ -37,11 +42,13 @@ abstract class AbstractDescriptor { public TypeDescriptor getElementTypeDescriptor() { if (isCollection()) { Class elementType = resolveCollectionElementType(); - return elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null; - } else if (isArray()) { + return (elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null); + } + else if (isArray()) { Class elementType = getType().getComponentType(); return new TypeDescriptor(nested(elementType, 0)); - } else { + } + else { return null; } } @@ -50,7 +57,8 @@ abstract class AbstractDescriptor { if (isMap()) { Class keyType = resolveMapKeyType(); return keyType != null ? new TypeDescriptor(nested(keyType, 0)) : null; - } else { + } + else { return null; } } @@ -59,7 +67,8 @@ abstract class AbstractDescriptor { if (isMap()) { Class valueType = resolveMapValueType(); return valueType != null ? new TypeDescriptor(nested(valueType, 1)) : null; - } else { + } + else { return null; } } @@ -70,12 +79,15 @@ abstract class AbstractDescriptor { if (isCollection()) { Class elementType = resolveCollectionElementType(); return elementType != null ? nested(elementType, 0) : null; - } else if (isArray()) { + } + else if (isArray()) { return nested(getType().getComponentType(), 0); - } else if (isMap()) { + } + else if (isMap()) { Class mapValueType = resolveMapValueType(); return mapValueType != null ? nested(mapValueType, 1) : null; - } else { + } + else { throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types"); } } @@ -104,4 +116,4 @@ abstract class AbstractDescriptor { return Map.class.isAssignableFrom(getType()); } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java index aa40d92d1d6..527fced98bd 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ClassDescriptor.java @@ -13,10 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; +/** + * @author Keith Donald + * @since 3.1 + */ class ClassDescriptor extends AbstractDescriptor { ClassDescriptor(Class type) { @@ -48,4 +53,4 @@ class ClassDescriptor extends AbstractDescriptor { return new ClassDescriptor(type); } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java index a03c561ebf2..653e494fd8f 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/FieldDescriptor.java @@ -13,23 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; import org.springframework.core.GenericCollectionTypeResolver; +/** + * @author Keith Donald + * @since 3.1 + */ class FieldDescriptor extends AbstractDescriptor { private final Field field; private final int nestingLevel; + private Map typeIndexesPerLevel; + + public FieldDescriptor(Field field) { - this(field.getType(), field, 1, 0); + super(field.getType()); + this.field = field; + this.nestingLevel = 1; } + private FieldDescriptor(Class type, Field field, int nestingLevel, int typeIndex, Map typeIndexesPerLevel) { + super(type); + this.field = field; + this.nestingLevel = nestingLevel; + this.typeIndexesPerLevel = typeIndexesPerLevel; + this.typeIndexesPerLevel.put(nestingLevel, typeIndex); + } + + @Override public Annotation[] getAnnotations() { return TypeDescriptor.nullSafeAnnotations(field.getAnnotations()); @@ -37,31 +58,25 @@ class FieldDescriptor extends AbstractDescriptor { @Override protected Class resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel); + return GenericCollectionTypeResolver.getCollectionFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); } @Override protected Class resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel); + return GenericCollectionTypeResolver.getMapKeyFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); } @Override protected Class resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel); + return GenericCollectionTypeResolver.getMapValueFieldType(this.field, this.nestingLevel, this.typeIndexesPerLevel); } @Override protected AbstractDescriptor nested(Class type, int typeIndex) { - return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex); - } - - // internal - - private FieldDescriptor(Class type, Field field, int nestingLevel, int typeIndex) { - super(type); - this.field = field; - this.nestingLevel = nestingLevel; - // TODO typeIndex is not preserved at current nestingLevel is not preserved: see SPR-8394 + if (this.typeIndexesPerLevel == null) { + this.typeIndexesPerLevel = new HashMap(4); + } + return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex, this.typeIndexesPerLevel); } -} \ No newline at end of file +} diff --git a/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java index 93f12d181b5..5f0fa28174a 100644 --- a/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java +++ b/org.springframework.core/src/main/java/org/springframework/core/convert/ParameterDescriptor.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.convert; import java.lang.annotation.Annotation; @@ -20,41 +21,52 @@ import java.lang.annotation.Annotation; import org.springframework.core.GenericCollectionTypeResolver; import org.springframework.core.MethodParameter; +/** + * @author Keith Donald + * @since 3.1 + */ class ParameterDescriptor extends AbstractDescriptor { private final MethodParameter methodParameter; + public ParameterDescriptor(MethodParameter methodParameter) { super(methodParameter.getParameterType()); if (methodParameter.getNestingLevel() != 1) { - throw new IllegalArgumentException("The MethodParameter argument must have its nestingLevel set to 1"); + throw new IllegalArgumentException("MethodParameter argument must have its nestingLevel set to 1"); } this.methodParameter = methodParameter; } + private ParameterDescriptor(Class type, MethodParameter methodParameter) { + super(type); + this.methodParameter = methodParameter; + } + + @Override public Annotation[] getAnnotations() { - if (methodParameter.getParameterIndex() == -1) { - return TypeDescriptor.nullSafeAnnotations(methodParameter.getMethodAnnotations()); + if (this.methodParameter.getParameterIndex() == -1) { + return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getMethodAnnotations()); } else { - return TypeDescriptor.nullSafeAnnotations(methodParameter.getParameterAnnotations()); + return TypeDescriptor.nullSafeAnnotations(this.methodParameter.getParameterAnnotations()); } } @Override protected Class resolveCollectionElementType() { - return GenericCollectionTypeResolver.getCollectionParameterType(methodParameter); + return GenericCollectionTypeResolver.getCollectionParameterType(this.methodParameter); } @Override protected Class resolveMapKeyType() { - return GenericCollectionTypeResolver.getMapKeyParameterType(methodParameter); + return GenericCollectionTypeResolver.getMapKeyParameterType(this.methodParameter); } @Override protected Class resolveMapValueType() { - return GenericCollectionTypeResolver.getMapValueParameterType(methodParameter); + return GenericCollectionTypeResolver.getMapValueParameterType(this.methodParameter); } @Override @@ -65,11 +77,4 @@ class ParameterDescriptor extends AbstractDescriptor { return new ParameterDescriptor(type, methodParameter); } - // internal - - private ParameterDescriptor(Class type, MethodParameter methodParameter) { - super(type); - this.methodParameter = methodParameter; - } - -} \ No newline at end of file +}