View Javadoc
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