1 /*
2 * Copyright 2002-2008 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.core;
18
19 import java.lang.reflect.Array;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.GenericArrayType;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.ParameterizedType;
24 import java.lang.reflect.Type;
25 import java.lang.reflect.TypeVariable;
26 import java.lang.reflect.WildcardType;
27 import java.util.Collection;
28 import java.util.Map;
29
30 /**
31 * Helper class for determining element types of collections and maps.
32 *
33 * <p>Mainly intended for usage within the framework, determining the
34 * target type of values to be added to a collection or map
35 * (to be able to attempt type conversion if appropriate).
36 *
37 * <p>Only usable on Java 5. Use an appropriate JdkVersion check before
38 * calling this class, if a fallback for JDK 1.4 is desirable.
39 *
40 * @author Juergen Hoeller
41 * @since 2.0
42 * @see org.springframework.beans.BeanWrapperImpl
43 * @see JdkVersion
44 */
45 public abstract class GenericCollectionTypeResolver {
46
47 /**
48 * Determine the generic element type of the given Collection class
49 * (if it declares one through a generic superclass or generic interface).
50 * @param collectionClass the collection class to introspect
51 * @return the generic type, or <code>null</code> if none
52 */
53 public static Class getCollectionType(Class collectionClass) {
54 return extractTypeFromClass(collectionClass, Collection.class, 0);
55 }
56
57 /**
58 * Determine the generic key type of the given Map class
59 * (if it declares one through a generic superclass or generic interface).
60 * @param mapClass the map class to introspect
61 * @return the generic type, or <code>null</code> if none
62 */
63 public static Class getMapKeyType(Class mapClass) {
64 return extractTypeFromClass(mapClass, Map.class, 0);
65 }
66
67 /**
68 * Determine the generic value type of the given Map class
69 * (if it declares one through a generic superclass or generic interface).
70 * @param mapClass the map class to introspect
71 * @return the generic type, or <code>null</code> if none
72 */
73 public static Class getMapValueType(Class mapClass) {
74 return extractTypeFromClass(mapClass, Map.class, 1);
75 }
76
77 /**
78 * Determine the generic element type of the given Collection field.
79 * @param collectionField the collection field to introspect
80 * @return the generic type, or <code>null</code> if none
81 */
82 public static Class getCollectionFieldType(Field collectionField) {
83 return getGenericFieldType(collectionField, Collection.class, 0, 1);
84 }
85
86 /**
87 * Determine the generic element type of the given Collection field.
88 * @param collectionField the collection field to introspect
89 * @param nestingLevel the nesting level of the target type
90 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
91 * nested List, whereas 2 would indicate the element of the nested List)
92 * @return the generic type, or <code>null</code> if none
93 */
94 public static Class getCollectionFieldType(Field collectionField, int nestingLevel) {
95 return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel);
96 }
97
98 /**
99 * Determine the generic key type of the given Map field.
100 * @param mapField the map field to introspect
101 * @return the generic type, or <code>null</code> if none
102 */
103 public static Class getMapKeyFieldType(Field mapField) {
104 return getGenericFieldType(mapField, Map.class, 0, 1);
105 }
106
107 /**
108 * Determine the generic key type of the given Map field.
109 * @param mapField the map field to introspect
110 * @param nestingLevel the nesting level of the target type
111 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
112 * nested List, whereas 2 would indicate the element of the nested List)
113 * @return the generic type, or <code>null</code> if none
114 */
115 public static Class getMapKeyFieldType(Field mapField, int nestingLevel) {
116 return getGenericFieldType(mapField, Map.class, 0, nestingLevel);
117 }
118
119 /**
120 * Determine the generic value type of the given Map field.
121 * @param mapField the map field to introspect
122 * @return the generic type, or <code>null</code> if none
123 */
124 public static Class getMapValueFieldType(Field mapField) {
125 return getGenericFieldType(mapField, Map.class, 1, 1);
126 }
127
128 /**
129 * Determine the generic value 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 * @return the generic type, or <code>null</code> if none
135 */
136 public static Class getMapValueFieldType(Field mapField, int nestingLevel) {
137 return getGenericFieldType(mapField, Map.class, 1, nestingLevel);
138 }
139
140 /**
141 * Determine the generic element type of the given Collection parameter.
142 * @param methodParam the method parameter specification
143 * @return the generic type, or <code>null</code> if none
144 */
145 public static Class getCollectionParameterType(MethodParameter methodParam) {
146 return getGenericParameterType(methodParam, Collection.class, 0);
147 }
148
149 /**
150 * Determine the generic key type of the given Map parameter.
151 * @param methodParam the method parameter specification
152 * @return the generic type, or <code>null</code> if none
153 */
154 public static Class getMapKeyParameterType(MethodParameter methodParam) {
155 return getGenericParameterType(methodParam, Map.class, 0);
156 }
157
158 /**
159 * Determine the generic value type of the given Map parameter.
160 * @param methodParam the method parameter specification
161 * @return the generic type, or <code>null</code> if none
162 */
163 public static Class getMapValueParameterType(MethodParameter methodParam) {
164 return getGenericParameterType(methodParam, Map.class, 1);
165 }
166
167 /**
168 * Determine the generic element type of the given Collection return type.
169 * @param method the method to check the return type for
170 * @return the generic type, or <code>null</code> if none
171 */
172 public static Class getCollectionReturnType(Method method) {
173 return getGenericReturnType(method, Collection.class, 0, 1);
174 }
175
176 /**
177 * Determine the generic element type of the given Collection return type.
178 * <p>If the specified nesting level is higher than 1, the element type of
179 * a nested Collection/Map will be analyzed.
180 * @param method the method to check the return type for
181 * @param nestingLevel the nesting level of the target type
182 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
183 * nested List, whereas 2 would indicate the element of the nested List)
184 * @return the generic type, or <code>null</code> if none
185 */
186 public static Class getCollectionReturnType(Method method, int nestingLevel) {
187 return getGenericReturnType(method, Collection.class, 0, nestingLevel);
188 }
189
190 /**
191 * Determine the generic key type of the given Map return type.
192 * @param method the method to check the return type for
193 * @return the generic type, or <code>null</code> if none
194 */
195 public static Class getMapKeyReturnType(Method method) {
196 return getGenericReturnType(method, Map.class, 0, 1);
197 }
198
199 /**
200 * Determine the generic key type of the given Map return type.
201 * @param method the method to check the return type for
202 * @param nestingLevel the nesting level of the target type
203 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
204 * nested List, whereas 2 would indicate the element of the nested List)
205 * @return the generic type, or <code>null</code> if none
206 */
207 public static Class getMapKeyReturnType(Method method, int nestingLevel) {
208 return getGenericReturnType(method, Map.class, 0, nestingLevel);
209 }
210
211 /**
212 * Determine the generic value type of the given Map return type.
213 * @param method the method to check the return type for
214 * @return the generic type, or <code>null</code> if none
215 */
216 public static Class getMapValueReturnType(Method method) {
217 return getGenericReturnType(method, Map.class, 1, 1);
218 }
219
220 /**
221 * Determine the generic value type of the given Map return type.
222 * @param method the method to check the return type for
223 * @param nestingLevel the nesting level of the target type
224 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
225 * nested List, whereas 2 would indicate the element of the nested List)
226 * @return the generic type, or <code>null</code> if none
227 */
228 public static Class getMapValueReturnType(Method method, int nestingLevel) {
229 return getGenericReturnType(method, Map.class, 1, nestingLevel);
230 }
231
232
233 /**
234 * Extract the generic parameter type from the given method or constructor.
235 * @param methodParam the method parameter specification
236 * @param source the source class/interface defining the generic parameter types
237 * @param typeIndex the index of the type (e.g. 0 for Collections,
238 * 0 for Map keys, 1 for Map values)
239 * @return the generic type, or <code>null</code> if none
240 */
241 private static Class getGenericParameterType(MethodParameter methodParam, Class source, int typeIndex) {
242 return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam),
243 source, typeIndex, methodParam.getNestingLevel(), 1);
244 }
245
246 /**
247 * Extract the generic type from the given field.
248 * @param field the field to check the type for
249 * @param source the source class/interface defining the generic parameter types
250 * @param typeIndex the index of the type (e.g. 0 for Collections,
251 * 0 for Map keys, 1 for Map values)
252 * @param nestingLevel the nesting level of the target type
253 * @return the generic type, or <code>null</code> if none
254 */
255 private static Class getGenericFieldType(Field field, Class source, int typeIndex, int nestingLevel) {
256 return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1);
257 }
258
259 /**
260 * Extract the generic return type from the given method.
261 * @param method the method to check the return type for
262 * @param source the source class/interface defining the generic parameter types
263 * @param typeIndex the index of the type (e.g. 0 for Collections,
264 * 0 for Map keys, 1 for Map values)
265 * @param nestingLevel the nesting level of the target type
266 * @return the generic type, or <code>null</code> if none
267 */
268 private static Class getGenericReturnType(Method method, Class source, int typeIndex, int nestingLevel) {
269 return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1);
270 }
271
272 /**
273 * Extract the generic type from the given Type object.
274 * @param methodParam the method parameter specification
275 * @param type the Type to check
276 * @param source the source collection/map Class that we check
277 * @param typeIndex the index of the actual type argument
278 * @param nestingLevel the nesting level of the target type
279 * @param currentLevel the current nested level
280 * @return the generic type as Class, or <code>null</code> if none
281 */
282 private static Class extractType(
283 MethodParameter methodParam, Type type, Class source, int typeIndex, int nestingLevel, int currentLevel) {
284
285 Type resolvedType = type;
286 if (type instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
287 Type mappedType = (Type) methodParam.typeVariableMap.get(type);
288 if (mappedType != null) {
289 resolvedType = mappedType;
290 }
291 }
292 if (resolvedType instanceof ParameterizedType) {
293 return extractTypeFromParameterizedType(
294 methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel);
295 }
296 else if (resolvedType instanceof Class) {
297 return extractTypeFromClass(methodParam, (Class) resolvedType, source, typeIndex, nestingLevel, currentLevel);
298 }
299 else {
300 return null;
301 }
302 }
303
304 /**
305 * Extract the generic type from the given ParameterizedType object.
306 * @param methodParam the method parameter specification
307 * @param ptype the ParameterizedType to check
308 * @param source the expected raw source type (can be <code>null</code>)
309 * @param typeIndex the index of the actual type argument
310 * @param nestingLevel the nesting level of the target type
311 * @param currentLevel the current nested level
312 * @return the generic type as Class, or <code>null</code> if none
313 */
314 private static Class extractTypeFromParameterizedType(MethodParameter methodParam,
315 ParameterizedType ptype, Class source, int typeIndex, int nestingLevel, int currentLevel) {
316
317 if (!(ptype.getRawType() instanceof Class)) {
318 return null;
319 }
320 Class rawType = (Class) ptype.getRawType();
321 Type[] paramTypes = ptype.getActualTypeArguments();
322 if (nestingLevel - currentLevel > 0) {
323 int nextLevel = currentLevel + 1;
324 Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null);
325 // Default is last parameter type: Collection element or Map value.
326 int indexToUse = (currentTypeIndex != null ? currentTypeIndex.intValue() : paramTypes.length - 1);
327 Type paramType = paramTypes[indexToUse];
328 return extractType(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel);
329 }
330 if (source != null && !source.isAssignableFrom(rawType)) {
331 return null;
332 }
333 Class fromSuperclassOrInterface =
334 extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel);
335 if (fromSuperclassOrInterface != null) {
336 return fromSuperclassOrInterface;
337 }
338 if (paramTypes == null || typeIndex >= paramTypes.length) {
339 return null;
340 }
341 Type paramType = paramTypes[typeIndex];
342 if (paramType instanceof TypeVariable && methodParam != null && methodParam.typeVariableMap != null) {
343 Type mappedType = (Type) methodParam.typeVariableMap.get(paramType);
344 if (mappedType != null) {
345 paramType = mappedType;
346 }
347 }
348 if (paramType instanceof WildcardType) {
349 Type[] lowerBounds = ((WildcardType) paramType).getLowerBounds();
350 if (lowerBounds != null && lowerBounds.length > 0) {
351 paramType = lowerBounds[0];
352 }
353 }
354 if (paramType instanceof ParameterizedType) {
355 paramType = ((ParameterizedType) paramType).getRawType();
356 }
357 if (paramType instanceof GenericArrayType) {
358 // A generic array type... Let's turn it into a straight array type if possible.
359 Type compType = ((GenericArrayType) paramType).getGenericComponentType();
360 if (compType instanceof Class) {
361 return Array.newInstance((Class) compType, 0).getClass();
362 }
363 }
364 else if (paramType instanceof Class) {
365 // We finally got a straight Class...
366 return (Class) paramType;
367 }
368 return null;
369 }
370
371 /**
372 * Extract the generic type from the given Class object.
373 * @param clazz the Class to check
374 * @param source the expected raw source type (can be <code>null</code>)
375 * @param typeIndex the index of the actual type argument
376 * @return the generic type as Class, or <code>null</code> if none
377 */
378 private static Class extractTypeFromClass(Class clazz, Class source, int typeIndex) {
379 return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1);
380 }
381
382 /**
383 * Extract the generic type from the given Class object.
384 * @param methodParam the method parameter specification
385 * @param clazz the Class to check
386 * @param source the expected raw source type (can be <code>null</code>)
387 * @param typeIndex the index of the actual type argument
388 * @param nestingLevel the nesting level of the target type
389 * @param currentLevel the current nested level
390 * @return the generic type as Class, or <code>null</code> if none
391 */
392 private static Class extractTypeFromClass(
393 MethodParameter methodParam, Class clazz, Class source, int typeIndex, int nestingLevel, int currentLevel) {
394
395 if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass())) {
396 return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel);
397 }
398 Type[] ifcs = clazz.getGenericInterfaces();
399 if (ifcs != null) {
400 for (int i = 0; i < ifcs.length; i++) {
401 Type ifc = ifcs[i];
402 Type rawType = ifc;
403 if (ifc instanceof ParameterizedType) {
404 rawType = ((ParameterizedType) ifc).getRawType();
405 }
406 if (rawType instanceof Class && isIntrospectionCandidate((Class) rawType)) {
407 return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel);
408 }
409 }
410 }
411 return null;
412 }
413
414 /**
415 * Determine whether the given class is a potential candidate
416 * that defines generic collection or map types.
417 * @param clazz the class to check
418 * @return whethe the given class is assignable to Collection or Map
419 */
420 private static boolean isIntrospectionCandidate(Class clazz) {
421 return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz));
422 }
423
424 }