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