full support for arbitrary nesting of collections in fields (SPR-8394); proper type detection in nested collections within arrays

This commit is contained in:
Juergen Hoeller 2011-06-07 22:33:23 +00:00
parent 15e009f3a0
commit d940811d8b
6 changed files with 167 additions and 81 deletions

View File

@ -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 <code>null</code> 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 <code>null</code> 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 <code>null</code> if none
*/
public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel);
}
/**
@ -96,7 +110,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or <code>null</code> 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 <code>null</code> 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 <code>null</code> if none
*/
public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel);
}
/**
@ -117,7 +145,7 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or <code>null</code> 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 <code>null</code> 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 <code>null</code> if none
*/
public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel);
}
/**
@ -234,8 +276,8 @@ public abstract class GenericCollectionTypeResolver {
* @return the generic type, or <code>null</code> 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 <code>null</code> 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<Integer, Integer> 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 <code>null</code> 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 <code>null</code> 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<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> 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 <code>null</code>)
* @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 <code>null</code> 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<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> 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 <code>null</code> 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 <code>null</code>)
* @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 <code>null</code> 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<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> 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);
}
}
}

View File

@ -60,12 +60,11 @@ public class MethodParameter {
private int nestingLevel = 1;
/** Map from Integer level to Integer type index */
private Map<Integer,Integer> typeIndexesPerLevel;
Map<Integer, Integer> typeIndexesPerLevel;
Map<TypeVariable, Type> 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;
}
}

View File

@ -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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<Integer, Integer> 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<Integer, Integer> 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);
if (this.typeIndexesPerLevel == null) {
this.typeIndexesPerLevel = new HashMap<Integer, Integer>(4);
}
return new FieldDescriptor(type, this.field, this.nestingLevel + 1, typeIndex, this.typeIndexesPerLevel);
}
// 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
}
}
}

View File

@ -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;
}
}
}