1 /*
2 * $Id: GenericsUtils.java 19191 2010-08-25 21:05:23Z tcarlson $
3 * --------------------------------------------------------------------------------------
4 * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
5 *
6 * The software in this package is published under the terms of the CPAL v1.0
7 * license, a copy of which has been included with this distribution in the
8 * LICENSE.txt file.
9 */
10 package org.mule.util.generics;
11
12 import java.lang.reflect.Array;
13 import java.lang.reflect.Field;
14 import java.lang.reflect.GenericArrayType;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.ParameterizedType;
17 import java.lang.reflect.Type;
18 import java.lang.reflect.TypeVariable;
19 import java.lang.reflect.WildcardType;
20 import java.util.Collection;
21 import java.util.Map;
22
23 /**
24 * Helper class for determining element types of collections and maps.
25 * <p/>
26 * <p>Mainly intended for usage within the framework, determining the
27 * target type of values to be added to a collection or map
28 * (to be able to attempt type conversion if appropriate).
29 * <p/>
30 * author: Spring
31 */
32 public class GenericsUtils
33 {
34
35
36 /**
37 * Determine the generic element type of the given Collection class
38 * (if it declares one through a generic superclass or generic interface).
39 *
40 * @param collectionClass the collection class to introspect
41 * @return the generic type, or <code>null</code> if none
42 */
43 public static Class<?> getCollectionType(Class<? extends Collection<?>> collectionClass)
44 {
45 return extractTypeFromClass(collectionClass, Collection.class, 0);
46 }
47
48 /**
49 * Determine the generic key type of the given Map class
50 * (if it declares one through a generic superclass or generic interface).
51 *
52 * @param mapClass the map class to introspect
53 * @return the generic type, or <code>null</code> if none
54 */
55 public static Class<?> getMapKeyType(Class<? extends Map<?, ?>> mapClass)
56 {
57 return extractTypeFromClass(mapClass, Map.class, 0);
58 }
59
60 /**
61 * Determine the generic value type of the given Map class
62 * (if it declares one through a generic superclass or generic interface).
63 *
64 * @param mapClass the map class to introspect
65 * @return the generic type, or <code>null</code> if none
66 */
67 public static Class<?> getMapValueType(Class<? extends Map<?, ?>> mapClass)
68 {
69 return extractTypeFromClass(mapClass, Map.class, 1);
70 }
71
72 /**
73 * Determine the generic element type of the given Collection field.
74 *
75 * @param collectionField the collection field to introspect
76 * @return the generic type, or <code>null</code> if none
77 */
78 public static Class<?> getCollectionFieldType(Field collectionField)
79 {
80 return getGenericFieldType(collectionField, Collection.class, 0, 1);
81 }
82
83 /**
84 * Determine the generic element type of the given Collection field.
85 *
86 * @param collectionField the collection field to introspect
87 * @param nestingLevel the nesting level of the target type
88 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
89 * nested List, whereas 2 would indicate the element of the nested List)
90 * @return the generic type, or <code>null</code> if none
91 */
92 public static Class<?> getCollectionFieldType(Field collectionField, int nestingLevel)
93 {
94 return getGenericFieldType(collectionField, Collection.class, 0, nestingLevel);
95 }
96
97 /**
98 * Determine the generic key type of the given Map field.
99 *
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 {
105 return getGenericFieldType(mapField, Map.class, 0, 1);
106 }
107
108 /**
109 * Determine the generic key type of the given Map field.
110 *
111 * @param mapField the map field to introspect
112 * @param nestingLevel the nesting level of the target type
113 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
114 * nested List, whereas 2 would indicate the element of the nested List)
115 * @return the generic type, or <code>null</code> if none
116 */
117 public static Class<?> getMapKeyFieldType(Field mapField, int nestingLevel)
118 {
119 return getGenericFieldType(mapField, Map.class, 0, nestingLevel);
120 }
121
122 /**
123 * Determine the generic value type of the given Map field.
124 *
125 * @param mapField the map field to introspect
126 * @return the generic type, or <code>null</code> if none
127 */
128 public static Class<?> getMapValueFieldType(Field mapField)
129 {
130 return getGenericFieldType(mapField, Map.class, 1, 1);
131 }
132
133 /**
134 * Determine the generic value type of the given Map field.
135 *
136 * @param mapField the map field to introspect
137 * @param nestingLevel the nesting level of the target type
138 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
139 * nested List, whereas 2 would indicate the element of the nested List)
140 * @return the generic type, or <code>null</code> if none
141 */
142 public static Class<?> getMapValueFieldType(Field mapField, int nestingLevel)
143 {
144 return getGenericFieldType(mapField, Map.class, 1, nestingLevel);
145 }
146
147 /**
148 * Determine the generic element type of the given Collection parameter.
149 *
150 * @param methodParam the method parameter specification
151 * @return the generic type, or <code>null</code> if none
152 */
153 public static Class<?> getCollectionParameterType(MethodParameter methodParam)
154 {
155 return getGenericParameterType(methodParam, Collection.class, 0);
156 }
157
158 /**
159 * Determine the generic key type of the given Map parameter.
160 *
161 * @param methodParam the method parameter specification
162 * @return the generic type, or <code>null</code> if none
163 */
164 public static Class<?> getMapKeyParameterType(MethodParameter methodParam)
165 {
166 return getGenericParameterType(methodParam, Map.class, 0);
167 }
168
169 /**
170 * Determine the generic value type of the given Map parameter.
171 *
172 * @param methodParam the method parameter specification
173 * @return the generic type, or <code>null</code> if none
174 */
175 public static Class<?> getMapValueParameterType(MethodParameter methodParam)
176 {
177 return getGenericParameterType(methodParam, Map.class, 1);
178 }
179
180 /**
181 * Determine the generic element type of the given Collection return type.
182 *
183 * @param method the method to check the return type for
184 * @return the generic type, or <code>null</code> if none
185 */
186 public static Class<?> getCollectionReturnType(Method method)
187 {
188 return getGenericReturnType(method, Collection.class, 0, 1);
189 }
190
191 /**
192 * Determine the generic element type of the given Collection return type.
193 * <p>If the specified nesting level is higher than 1, the element type of
194 * a nested Collection/Map will be analyzed.
195 *
196 * @param method the method to check the return type for
197 * @param nestingLevel the nesting level of the target type
198 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
199 * nested List, whereas 2 would indicate the element of the nested List)
200 * @return the generic type, or <code>null</code> if none
201 */
202 public static Class<?> getCollectionReturnType(Method method, int nestingLevel)
203 {
204 return getGenericReturnType(method, Collection.class, 0, nestingLevel);
205 }
206
207 /**
208 * Determine the generic key type of the given Map return type.
209 *
210 * @param method the method to check the return type for
211 * @return the generic type, or <code>null</code> if none
212 */
213 public static Class<?> getMapKeyReturnType(Method method)
214 {
215 return getGenericReturnType(method, Map.class, 0, 1);
216 }
217
218 /**
219 * Determine the generic key type of the given Map return type.
220 *
221 * @param method the method to check the return type for
222 * @param nestingLevel the nesting level of the target type
223 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
224 * nested List, whereas 2 would indicate the element of the nested List)
225 * @return the generic type, or <code>null</code> if none
226 */
227 public static Class<?> getMapKeyReturnType(Method method, int nestingLevel)
228 {
229 return getGenericReturnType(method, Map.class, 0, nestingLevel);
230 }
231
232 /**
233 * Determine the generic value type of the given Map return type.
234 *
235 * @param method the method to check the return type for
236 * @return the generic type, or <code>null</code> if none
237 */
238 public static Class<?> getMapValueReturnType(Method method)
239 {
240 return getGenericReturnType(method, Map.class, 1, 1);
241 }
242
243 /**
244 * Determine the generic value type of the given Map return type.
245 *
246 * @param method the method to check the return type for
247 * @param nestingLevel the nesting level of the target type
248 * (typically 1; e.g. in case of a List of Lists, 1 would indicate the
249 * nested List, whereas 2 would indicate the element of the nested List)
250 * @return the generic type, or <code>null</code> if none
251 */
252 public static Class<?> getMapValueReturnType(Method method, int nestingLevel)
253 {
254 return getGenericReturnType(method, Map.class, 1, nestingLevel);
255 }
256
257
258 /**
259 * Extract the generic parameter type from the given method or constructor.
260 *
261 * @param methodParam the method parameter specification
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 * @return the generic type, or <code>null</code> if none
266 */
267 private static Class<?> getGenericParameterType(MethodParameter methodParam, Class<?> source, int typeIndex)
268 {
269 return extractType(methodParam, GenericTypeResolver.getTargetType(methodParam),
270 source, typeIndex, methodParam.getNestingLevel(), 1);
271 }
272
273 /**
274 * Extract the generic type from the given field.
275 *
276 * @param field the field to check the type for
277 * @param source the source class/interface defining the generic parameter types
278 * @param typeIndex the index of the type (e.g. 0 for Collections,
279 * 0 for Map keys, 1 for Map values)
280 * @param nestingLevel the nesting level of the target type
281 * @return the generic type, or <code>null</code> if none
282 */
283 private static Class<?> getGenericFieldType(Field field, Class<?> source, int typeIndex, int nestingLevel)
284 {
285 return extractType(null, field.getGenericType(), source, typeIndex, nestingLevel, 1);
286 }
287
288 /**
289 * Extract the generic return type from the given method.
290 *
291 * @param method the method to check the return type for
292 * @param source the source class/interface defining the generic parameter types
293 * @param typeIndex the index of the type (e.g. 0 for Collections,
294 * 0 for Map keys, 1 for Map values)
295 * @param nestingLevel the nesting level of the target type
296 * @return the generic type, or <code>null</code> if none
297 */
298 private static Class<?> getGenericReturnType(Method method, Class<?> source, int typeIndex, int nestingLevel)
299 {
300 return extractType(null, method.getGenericReturnType(), source, typeIndex, nestingLevel, 1);
301 }
302
303 /**
304 * Extract the generic type from the given Type object.
305 *
306 * @param methodParam the method parameter specification
307 * @param type the Type to check
308 * @param source the source collection/map Class<?> that we check
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<?> extractType(MethodParameter methodParam, Type type, Class<?> source,
315 int typeIndex, int nestingLevel, int currentLevel)
316 {
317 Type resolvedType = type;
318 if (type instanceof TypeVariable<?> && methodParam != null && methodParam.typeVariableMap != null)
319 {
320 Type mappedType = methodParam.typeVariableMap.get(type);
321 if (mappedType != null)
322 {
323 resolvedType = mappedType;
324 }
325 }
326 if (resolvedType instanceof ParameterizedType)
327 {
328 return extractTypeFromParameterizedType(
329 methodParam, (ParameterizedType) resolvedType, source, typeIndex, nestingLevel, currentLevel);
330 }
331 else if (resolvedType instanceof Class<?>)
332 {
333 Class<?> resolvedClass = (Class<?>) resolvedType;
334 return extractTypeFromClass(methodParam, resolvedClass, source, typeIndex, nestingLevel, currentLevel);
335 }
336 else
337 {
338 return null;
339 }
340 }
341
342 /**
343 * Extract the generic type from the given ParameterizedType object.
344 *
345 * @param methodParam the method parameter specification
346 * @param ptype the ParameterizedType to check
347 * @param source the expected raw source type (can be <code>null</code>)
348 * @param typeIndex the index of the actual type argument
349 * @param nestingLevel the nesting level of the target type
350 * @param currentLevel the current nested level
351 * @return the generic type as Class, or <code>null</code> if none
352 */
353 private static Class<?> extractTypeFromParameterizedType(MethodParameter methodParam,
354 ParameterizedType ptype, Class<?> source, int typeIndex, int nestingLevel, int currentLevel)
355 {
356
357 if (!(ptype.getRawType() instanceof Class<?>))
358 {
359 return null;
360 }
361
362 Class<?> rawType = (Class<?>) ptype.getRawType();
363 Type[] paramTypes = ptype.getActualTypeArguments();
364 if (nestingLevel - currentLevel > 0)
365 {
366 int nextLevel = currentLevel + 1;
367 Integer currentTypeIndex = (methodParam != null ? methodParam.getTypeIndexForLevel(nextLevel) : null);
368 // Default is last parameter type: Collection element or Map value.
369 int indexToUse = (currentTypeIndex != null ? currentTypeIndex : paramTypes.length - 1);
370 Type paramType = paramTypes[indexToUse];
371 return extractType(methodParam, paramType, source, typeIndex, nestingLevel, nextLevel);
372 }
373 if (source != null && !source.isAssignableFrom(rawType))
374 {
375 return null;
376 }
377 Class<?> fromSuperclassOrInterface =
378 extractTypeFromClass(methodParam, rawType, source, typeIndex, nestingLevel, currentLevel);
379 if (fromSuperclassOrInterface != null)
380 {
381 return fromSuperclassOrInterface;
382 }
383 if (paramTypes == null || typeIndex >= paramTypes.length)
384 {
385 return null;
386 }
387 Type paramType = paramTypes[typeIndex];
388 if (paramType instanceof TypeVariable<?> && methodParam != null && methodParam.typeVariableMap != null)
389 {
390 Type mappedType = methodParam.typeVariableMap.get(paramType);
391 if (mappedType != null)
392 {
393 paramType = mappedType;
394 }
395 }
396 if (paramType instanceof WildcardType)
397 {
398 WildcardType wildcardType = (WildcardType) paramType;
399 Type[] upperBounds = wildcardType.getUpperBounds();
400 if (upperBounds != null && upperBounds.length > 0 && !Object.class.equals(upperBounds[0]))
401 {
402 paramType = upperBounds[0];
403 }
404 else
405 {
406 Type[] lowerBounds = wildcardType.getLowerBounds();
407 if (lowerBounds != null && lowerBounds.length > 0 && !Object.class.equals(lowerBounds[0]))
408 {
409 paramType = lowerBounds[0];
410 }
411 }
412 }
413 if (paramType instanceof ParameterizedType)
414 {
415 paramType = ((ParameterizedType) paramType).getRawType();
416 }
417 if (paramType instanceof GenericArrayType)
418 {
419 // A generic array type... Let's turn it into a straight array type if possible.
420 Type compType = ((GenericArrayType) paramType).getGenericComponentType();
421 if (compType instanceof Class<?>)
422 {
423 Class<?> compClass = (Class<?>) compType;
424 return Array.newInstance(compClass, 0).getClass();
425 }
426 }
427 else if (paramType instanceof Class<?>)
428 {
429 // We finally got a straight Class...
430 return (Class<?>) paramType;
431 }
432 return null;
433 }
434
435 /**
436 * Extract the generic type from the given Class<?> object.
437 *
438 * @param clazz the Class<?> to check
439 * @param source the expected raw source type (can be <code>null</code>)
440 * @param typeIndex the index of the actual type argument
441 * @return the generic type as Class, or <code>null</code> if none
442 */
443 private static Class<?> extractTypeFromClass(Class<?> clazz, Class<?> source, int typeIndex)
444 {
445 return extractTypeFromClass(null, clazz, source, typeIndex, 1, 1);
446 }
447
448 /**
449 * Extract the generic type from the given Class<?> object.
450 *
451 * @param methodParam the method parameter specification
452 * @param clazz the Class<?> to check
453 * @param source the expected raw source type (can be <code>null</code>)
454 * @param typeIndex the index of the actual type argument
455 * @param nestingLevel the nesting level of the target type
456 * @param currentLevel the current nested level
457 * @return the generic type as Class, or <code>null</code> if none
458 */
459 private static Class<?> extractTypeFromClass(
460 MethodParameter methodParam, Class<?> clazz, Class<?> source, int typeIndex, int nestingLevel, int currentLevel)
461 {
462
463 if (clazz.getName().startsWith("java.util."))
464 {
465 return null;
466 }
467 if (clazz.getSuperclass() != null && isIntrospectionCandidate(clazz.getSuperclass()))
468 {
469 return extractType(methodParam, clazz.getGenericSuperclass(), source, typeIndex, nestingLevel, currentLevel);
470 }
471 Type[] ifcs = clazz.getGenericInterfaces();
472 if (ifcs != null)
473 {
474 for (Type ifc : ifcs)
475 {
476 Type rawType = ifc;
477 if (ifc instanceof ParameterizedType)
478 {
479 rawType = ((ParameterizedType) ifc).getRawType();
480 }
481 if (rawType instanceof Class<?> && isIntrospectionCandidate((Class<?>) rawType))
482 {
483 return extractType(methodParam, ifc, source, typeIndex, nestingLevel, currentLevel);
484 }
485 }
486 }
487 return null;
488 }
489
490 /**
491 * Determine whether the given class is a potential candidate
492 * that defines generic collection or map types.
493 *
494 * @param clazz the class to check
495 * @return whether the given class is assignable to Collection or Map
496 */
497 private static boolean isIntrospectionCandidate(Class<?> clazz)
498 {
499 return (Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz));
500 }
501
502 }
503