Skip to content

Commit d940811

Browse files
committed
full support for arbitrary nesting of collections in fields (SPR-8394); proper type detection in nested collections within arrays
1 parent 15e009f commit d940811

File tree

6 files changed

+167
-81
lines changed

6 files changed

+167
-81
lines changed

org.springframework.core/src/main/java/org/springframework/core/GenericCollectionTypeResolver.java

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2011 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -75,7 +75,7 @@ public static Class<?> getMapValueType(Class<? extends Map> mapClass) {
7575
* @return the generic type, or <code>null</code> if none
7676
*/
7777
public static Class<?> getCollectionFieldType(Field collectionField) {
78-
return getGenericFieldType(collectionField, Collection.class, 0, 1);
78+
return getGenericFieldType(collectionField, Collection.class, 0, null, 1);
7979
}
8080

8181
/**
@@ -87,7 +87,21 @@ public static Class<?> getCollectionFieldType(Field collectionField) {
8787
* @return the generic type, or <code>null</code> if none
8888
*/
8989
public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel) {
90-
return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel);
90+
return getGenericFieldType(collectionField, Collection.class, 0, null, nestingLevel);
91+
}
92+
93+
/**
94+
* Determine the generic element type of the given Collection field.
95+
* @param collectionField the collection field to introspect
96+
* @param nestingLevel the nesting level of the target type
97+
* (typically 1; e.g. in case of a List of Lists, 1 would indicate the
98+
* nested List, whereas 2 would indicate the element of the nested List)
99+
* @param typeIndexesPerLevel Map keyed by nesting level, with each value
100+
* expressing the type index for traversal at that level
101+
* @return the generic type, or <code>null</code> if none
102+
*/
103+
public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
104+
return getGenericFieldType(collectionField, Collection.class, 0, typeIndexesPerLevel, nestingLevel);
91105
}
92106

93107
/**
@@ -96,7 +110,7 @@ public static Class<?> getCollectionFieldType(Field collectionField, int nesting
96110
* @return the generic type, or <code>null</code> if none
97111
*/
98112
public static Class<?> getMapKeyFieldType(Field mapField) {
99-
return getGenericFieldType(mapField, Map.class, 0, 1);
113+
return getGenericFieldType(mapField, Map.class, 0, null, 1);
100114
}
101115

102116
/**
@@ -108,7 +122,21 @@ public static Class<?> getMapKeyFieldType(Field mapField) {
108122
* @return the generic type, or <code>null</code> if none
109123
*/
110124
public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel) {
111-
return getGenericFieldType(mapField, Map.class, 0, nestingLevel);
125+
return getGenericFieldType(mapField, Map.class, 0, null, nestingLevel);
126+
}
127+
128+
/**
129+
* Determine the generic key type of the given Map field.
130+
* @param mapField the map field to introspect
131+
* @param nestingLevel the nesting level of the target type
132+
* (typically 1; e.g. in case of a List of Lists, 1 would indicate the
133+
* nested List, whereas 2 would indicate the element of the nested List)
134+
* @param typeIndexesPerLevel Map keyed by nesting level, with each value
135+
* expressing the type index for traversal at that level
136+
* @return the generic type, or <code>null</code> if none
137+
*/
138+
public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
139+
return getGenericFieldType(mapField, Map.class, 0, typeIndexesPerLevel, nestingLevel);
112140
}
113141

114142
/**
@@ -117,7 +145,7 @@ public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel) {
117145
* @return the generic type, or <code>null</code> if none
118146
*/
119147
public static Class<?> getMapValueFieldType(Field mapField) {
120-
return getGenericFieldType(mapField, Map.class, 1, 1);
148+
return getGenericFieldType(mapField, Map.class, 1, null, 1);
121149
}
122150

123151
/**
@@ -129,7 +157,21 @@ public static Class<?> getMapValueFieldType(Field mapField) {
129157
* @return the generic type, or <code>null</code> if none
130158
*/
131159
public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel) {
132-
return getGenericFieldType(mapField, Map.class, 1, nestingLevel);
160+
return getGenericFieldType(mapField, Map.class, 1, null, nestingLevel);
161+
}
162+
163+
/**
164+
* Determine the generic value type of the given Map field.
165+
* @param mapField the map field to introspect
166+
* @param nestingLevel the nesting level of the target type
167+
* (typically 1; e.g. in case of a List of Lists, 1 would indicate the
168+
* nested List, whereas 2 would indicate the element of the nested List)
169+
* @param typeIndexesPerLevel Map keyed by nesting level, with each value
170+
* expressing the type index for traversal at that level
171+
* @return the generic type, or <code>null</code> if none
172+
*/
173+
public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel, Map<Integer, Integer> typeIndexesPerLevel) {
174+
return getGenericFieldType(mapField, Map.class, 1, typeIndexesPerLevel, nestingLevel);
133175
}
134176

135177
/**
@@ -234,8 +276,8 @@ public static Class<?> getMapValueReturnType(Method method, int nestingLevel) {
234276
* @return the generic type, or <code>null</code> if none
235277
*/
236278
private static Class<?> getGenericParameterType(MethodParameter methodParam, Class<?> source, int typeIndex) {
237-
return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam),
238-
source, typeIndex, methodParam.getNestingLevel(), 1);
279+
return extractType(GenericTypeResolver.getTargetType(methodParam), source, typeIndex,
280+
methodParam.typeVariableMap, methodParam.typeIndexesPerLevel, methodParam.getNestingLevel(), 1);
239281
}
240282

241283
/**
@@ -247,8 +289,9 @@ private static Class<?> getGenericParameterType(MethodParameter methodParam, Cla
247289
* @param nestingLevel the nesting level of the target type
248290
* @return the generic type, or <code>null</code> if none
249291
*/
250-
private static Class<?> getGenericFieldType(Field field, Class<?> source, int typeIndex, int nestingLevel) {
251-
return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1);
292+
private static Class<?> getGenericFieldType(Field field, Class<?> source, int typeIndex,
293+
Map<Integer, Integer> typeIndexesPerLevel, int nestingLevel) {
294+
return extractType(field.getGenericType(), source, typeIndex, null, typeIndexesPerLevel, nestingLevel, 1);
252295
}
253296

254297
/**
@@ -261,35 +304,40 @@ private static Class<?> getGenericFieldType(Field field, Class<?> source, int ty
261304
* @return the generic type, or <code>null</code> if none
262305
*/
263306
private static Class<?> getGenericReturnType(Method method, Class<?> source, int typeIndex, int nestingLevel) {
264-
return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1);
307+
return extractType(method.getGenericReturnType(), source, typeIndex, null, null, nestingLevel, 1);
265308
}
266309

267310
/**
268311
* Extract the generic type from the given Type object.
269-
* @param methodParam the method parameter specification
270312
* @param type the Type to check
271313
* @param source the source collection/map Class that we check
272314
* @param typeIndex the index of the actual type argument
273315
* @param nestingLevel the nesting level of the target type
274316
* @param currentLevel the current nested level
275317
* @return the generic type as Class, or <code>null</code> if none
276318
*/
277-
private static Class<?> extractType(
278-
MethodParameter methodParam, Type type, Class<?> source, int typeIndex, int nestingLevel, int currentLevel) {
319+
private static Class<?> extractType(Type type, Class<?> source, int typeIndex,
320+
Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
321+
int nestingLevel, int currentLevel) {
279322

280323
Type resolvedType = type;
281-
if (type instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
282-
Type mappedType = methodParam.typeVariableMap.get((TypeVariable) type);
324+
if (type instanceof TypeVariable && typeVariableMap != null) {
325+
Type mappedType = typeVariableMap.get((TypeVariable) type);
283326
if (mappedType != null) {
284327
resolvedType = mappedType;
285328
}
286329
}
287330
if (resolvedType instanceof ParameterizedType) {
288-
return extractTypeFromParameterizedType(
289-
methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel);
331+
return extractTypeFromParameterizedType((ParameterizedType) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
332+
nestingLevel, currentLevel);
290333
}
291334
else if (resolvedType instanceof Class) {
292-
return extractTypeFromClass(methodParam, (Class) resolvedType, source, typeIndex, nestingLevel, currentLevel);
335+
return extractTypeFromClass((Class) resolvedType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
336+
nestingLevel, currentLevel);
337+
}
338+
else if (resolvedType instanceof GenericArrayType) {
339+
Type compType = ((GenericArrayType) resolvedType).getGenericComponentType();
340+
return extractType(compType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel + 1);
293341
}
294342
else {
295343
return null;
@@ -298,16 +346,16 @@ else if (resolvedType instanceof Class) {
298346

299347
/**
300348
* Extract the generic type from the given ParameterizedType object.
301-
* @param methodParam the method parameter specification
302349
* @param ptype the ParameterizedType to check
303350
* @param source the expected raw source type (can be <code>null</code>)
304351
* @param typeIndex the index of the actual type argument
305352
* @param nestingLevel the nesting level of the target type
306353
* @param currentLevel the current nested level
307354
* @return the generic type as Class, or <code>null</code> if none
308355
*/
309-
private static Class<?> extractTypeFromParameterizedType(MethodParameter methodParam,
310-
ParameterizedType ptype, Class<?> source, int typeIndex, int nestingLevel, int currentLevel) {
356+
private static Class<?> extractTypeFromParameterizedType(ParameterizedType ptype, Class<?> source, int typeIndex,
357+
Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
358+
int nestingLevel, int currentLevel) {
311359

312360
if (!(ptype.getRawType() instanceof Class)) {
313361
return null;
@@ -316,26 +364,26 @@ private static Class<?> extractTypeFromParameterizedType(MethodParameter methodP
316364
Type[] paramTypes = ptype.getActualTypeArguments();
317365
if (nestingLevel - currentLevel > 0) {
318366
int nextLevel = currentLevel + 1;
319-
Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null);
367+
Integer currentTypeIndex = (typeIndexesPerLevel != null ? typeIndexesPerLevel.get(nextLevel) : null);
320368
// Default is last parameter type: Collection element or Map value.
321369
int indexToUse = (currentTypeIndex != null ? currentTypeIndex : paramTypes.length - 1);
322370
Type paramType = paramTypes[indexToUse];
323-
return extractType(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel);
371+
return extractType(paramType, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, nextLevel);
324372
}
325373
if (source != null && !source.isAssignableFrom(rawType)) {
326374
return null;
327375
}
328-
Class fromSuperclassOrInterface =
329-
extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel);
376+
Class fromSuperclassOrInterface = extractTypeFromClass(rawType, source, typeIndex, typeVariableMap, typeIndexesPerLevel,
377+
nestingLevel, currentLevel);
330378
if (fromSuperclassOrInterface != null) {
331379
return fromSuperclassOrInterface;
332380
}
333381
if (paramTypes == null || typeIndex >= paramTypes.length) {
334382
return null;
335383
}
336384
Type paramType = paramTypes[typeIndex];
337-
if (paramType instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
338-
Type mappedType = methodParam.typeVariableMap.get((TypeVariable) paramType);
385+
if (paramType instanceof TypeVariable && typeVariableMap != null) {
386+
Type mappedType = typeVariableMap.get((TypeVariable) paramType);
339387
if (mappedType != null) {
340388
paramType = mappedType;
341389
}
@@ -378,27 +426,28 @@ else if (paramType instanceof Class) {
378426
* @return the generic type as Class, or <code>null</code> if none
379427
*/
380428
private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex) {
381-
return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1);
429+
return extractTypeFromClass(clazz, source, typeIndex, null, null, 1, 1);
382430
}
383431

384432
/**
385433
* Extract the generic type from the given Class object.
386-
* @param methodParam the method parameter specification
387434
* @param clazz the Class to check
388435
* @param source the expected raw source type (can be <code>null</code>)
389436
* @param typeIndex the index of the actual type argument
390437
* @param nestingLevel the nesting level of the target type
391438
* @param currentLevel the current nested level
392439
* @return the generic type as Class, or <code>null</code> if none
393440
*/
394-
private static Class<?> extractTypeFromClass(
395-
MethodParameter methodParam, Class<?> clazz, Class<?> source, int typeIndex, int nestingLevel, int currentLevel) {
441+
private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex,
442+
Map<TypeVariable, Type> typeVariableMap, Map<Integer, Integer> typeIndexesPerLevel,
443+
int nestingLevel, int currentLevel) {
396444

397445
if (clazz.getName().startsWith("java.util.")) {
398446
return null;
399447
}
400448
if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) {
401-
return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel);
449+
return extractType(clazz.getGenericSuperclass(), source, typeIndex, typeVariableMap, typeIndexesPerLevel,
450+
nestingLevel, currentLevel);
402451
}
403452
Type[] ifcs = clazz.getGenericInterfaces();
404453
if (ifcs != null) {
@@ -408,7 +457,7 @@ private static Class<?> extractTypeFromClass(
408457
rawType = ((ParameterizedType) ifc).getRawType();
409458
}
410459
if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) {
411-
return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel);
460+
return extractType(ifc, source, typeIndex, typeVariableMap, typeIndexesPerLevel, nestingLevel, currentLevel);
412461
}
413462
}
414463
}

org.springframework.core/src/main/java/org/springframework/core/MethodParameter.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,11 @@ public class MethodParameter {
6060
private int nestingLevel = 1;
6161

6262
/** Map from Integer level to Integer type index */
63-
private Map<Integer,Integer> typeIndexesPerLevel;
63+
Map<Integer, Integer> typeIndexesPerLevel;
6464

6565
Map<TypeVariable, Type> typeVariableMap;
6666

67-
private int hash;
68-
67+
private int hash = 0;
6968

7069

7170
/**
@@ -440,12 +439,13 @@ else if (this.getMember().equals(other.getMember())) {
440439

441440
@Override
442441
public int hashCode() {
443-
int result = hash;
442+
int result = this.hash;
444443
if (result == 0) {
445444
result = getMember().hashCode();
446-
result = 31 * result + parameterIndex;
447-
hash = result;
445+
result = 31 * result + this.parameterIndex;
446+
this.hash = result;
448447
}
449448
return result;
450449
}
450+
451451
}

org.springframework.core/src/main/java/org/springframework/core/convert/AbstractDescriptor.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
1617
package org.springframework.core.convert;
1718

1819
import java.lang.annotation.Annotation;
1920
import java.util.Collection;
2021
import java.util.Map;
2122

23+
/**
24+
* @author Keith Donald
25+
* @since 3.1
26+
*/
2227
abstract class AbstractDescriptor {
2328

2429
private final Class<?> type;
@@ -37,11 +42,13 @@ public Class<?> getType() {
3742
public TypeDescriptor getElementTypeDescriptor() {
3843
if (isCollection()) {
3944
Class<?> elementType = resolveCollectionElementType();
40-
return elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null;
41-
} else if (isArray()) {
45+
return (elementType != null ? new TypeDescriptor(nested(elementType, 0)) : null);
46+
}
47+
else if (isArray()) {
4248
Class<?> elementType = getType().getComponentType();
4349
return new TypeDescriptor(nested(elementType, 0));
44-
} else {
50+
}
51+
else {
4552
return null;
4653
}
4754
}
@@ -50,7 +57,8 @@ public TypeDescriptor getMapKeyTypeDescriptor() {
5057
if (isMap()) {
5158
Class<?> keyType = resolveMapKeyType();
5259
return keyType != null ? new TypeDescriptor(nested(keyType, 0)) : null;
53-
} else {
60+
}
61+
else {
5462
return null;
5563
}
5664
}
@@ -59,7 +67,8 @@ public TypeDescriptor getMapValueTypeDescriptor() {
5967
if (isMap()) {
6068
Class<?> valueType = resolveMapValueType();
6169
return valueType != null ? new TypeDescriptor(nested(valueType, 1)) : null;
62-
} else {
70+
}
71+
else {
6372
return null;
6473
}
6574
}
@@ -70,12 +79,15 @@ public AbstractDescriptor nested() {
7079
if (isCollection()) {
7180
Class<?> elementType = resolveCollectionElementType();
7281
return elementType != null ? nested(elementType, 0) : null;
73-
} else if (isArray()) {
82+
}
83+
else if (isArray()) {
7484
return nested(getType().getComponentType(), 0);
75-
} else if (isMap()) {
85+
}
86+
else if (isMap()) {
7687
Class<?> mapValueType = resolveMapValueType();
7788
return mapValueType != null ? nested(mapValueType, 1) : null;
78-
} else {
89+
}
90+
else {
7991
throw new IllegalStateException("Not a collection, array, or map: cannot resolve nested value types");
8092
}
8193
}
@@ -104,4 +116,4 @@ private boolean isMap() {
104116
return Map.class.isAssignableFrom(getType());
105117
}
106118

107-
}
119+
}

0 commit comments

Comments
 (0)