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.ref.Reference;
10  import java.lang.ref.WeakReference;
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.util.Collections;
17  import java.util.HashMap;
18  import java.util.Map;
19  import java.util.WeakHashMap;
20  
21  /**
22   * Helper class for resolving generic types against type variables.
23   * <p/>
24   * <p>Mainly intended for usage within the framework, resolving method
25   * parameter types even when they are declared generically.
26   * <p/>
27   * author: Spring
28   */
29  public abstract class GenericTypeResolver
30  {
31  
32      /**
33       * Cache from Class to TypeVariable Map
34       */
35      private static final Map<Class, Reference<Map<TypeVariable, Type>>> typeVariableCache =
36              Collections.synchronizedMap(new WeakHashMap<Class, Reference<Map<TypeVariable, Type>>>());
37  
38  
39      private GenericTypeResolver()
40      {
41          // do not isntantiate
42      }
43  
44      /**
45       * Determine the target type for the given parameter specification.
46       *
47       * @param methodParam the method parameter specification
48       * @return the corresponding generic parameter type
49       */
50      public static Type getTargetType(MethodParameter methodParam)
51      {
52          if (methodParam.getConstructor() != null)
53          {
54              return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()];
55          }
56          else
57          {
58              if (methodParam.getParameterIndex() >= 0)
59              {
60                  return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()];
61              }
62              else
63              {
64                  return methodParam.getMethod().getGenericReturnType();
65              }
66          }
67      }
68  
69      /**
70       * Determine the target type for the given generic parameter type.
71       *
72       * @param methodParam the method parameter specification
73       * @param clazz       the class to resolve type variables against
74       * @return the corresponding generic parameter or return type
75       */
76      public static Class<?> resolveParameterType(MethodParameter methodParam, Class clazz)
77      {
78          Type genericType = getTargetType(methodParam);
79          Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
80          Type rawType = getRawType(genericType, typeVariableMap);
81          Class result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType());
82          methodParam.setParameterType(result);
83          methodParam.typeVariableMap = typeVariableMap;
84          return result;
85      }
86  
87      /**
88       * Determine the target type for the generic return type of the given method.
89       *
90       * @param method the method to introspect
91       * @param clazz  the class to resolve type variables against
92       * @return the corresponding generic parameter or return type
93       */
94      public static Class<?> resolveReturnType(Method method, Class clazz)
95      {
96          Type genericType = method.getGenericReturnType();
97          Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
98          Type rawType = getRawType(genericType, typeVariableMap);
99          return (rawType instanceof Class ? (Class) rawType : method.getReturnType());
100     }
101 
102     /**
103      * Resolve the single type argument of the given generic interface against
104      * the given target class which is assumed to implement the generic interface
105      * and possibly declare a concrete type for its type variable.
106      *
107      * @param clazz      the target class to check against
108      * @param genericIfc the generic interface to resolve the type argument from
109      * @return the resolved type of the argument, or <code>null</code> if not resolvable
110      */
111     public static Class<?> resolveTypeArgument(Class clazz, Class genericIfc)
112     {
113         Class[] typeArgs = resolveTypeArguments(clazz, genericIfc);
114         if (typeArgs == null)
115         {
116             return null;
117         }
118         if (typeArgs.length != 1)
119         {
120             throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
121                     genericIfc.getName() + "] but found " + typeArgs.length);
122         }
123         return typeArgs[0];
124     }
125 
126     /**
127      * Resolve the type arguments of the given generic interface against the given
128      * target class which is assumed to implement the generic interface and possibly
129      * declare concrete types for its type variables.
130      *
131      * @param clazz      the target class to check against
132      * @param genericIfc the generic interface to resolve the type argument from
133      * @return the resolved type of each argument, with the array size matching the
134      *         number of actual type arguments, or <code>null</code> if not resolvable
135      */
136     public static Class[] resolveTypeArguments(Class clazz, Class genericIfc)
137     {
138         return doResolveTypeArguments(clazz, clazz, genericIfc);
139     }
140 
141     private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc)
142     {
143         while (classToIntrospect != null)
144         {
145             Type[] ifcs = classToIntrospect.getGenericInterfaces();
146             for (Type ifc : ifcs)
147             {
148                 if (ifc instanceof ParameterizedType)
149                 {
150                     ParameterizedType paramIfc = (ParameterizedType) ifc;
151                     Type rawType = paramIfc.getRawType();
152                     if (genericIfc.equals(rawType))
153                     {
154                         Type[] typeArgs = paramIfc.getActualTypeArguments();
155                         Class[] result = new Class[typeArgs.length];
156                         for (int i = 0; i < typeArgs.length; i++)
157                         {
158                             Type arg = typeArgs[i];
159                             if (arg instanceof TypeVariable)
160                             {
161                                 arg = getTypeVariableMap(ownerClass).get((TypeVariable) arg);
162                             }
163                             result[i] = (arg instanceof Class ? (Class) arg : Object.class);
164                         }
165                         return result;
166                     }
167                     else if (genericIfc.isAssignableFrom((Class) rawType))
168                     {
169                         return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc);
170                     }
171                 }
172                 else if (genericIfc.isAssignableFrom((Class) ifc))
173                 {
174                     return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc);
175                 }
176             }
177             classToIntrospect = classToIntrospect.getSuperclass();
178         }
179         return null;
180     }
181 
182 
183     /**
184      * Resolve the specified generic type against the given TypeVariable map.
185      *
186      * @param genericType     the generic type to resolve
187      * @param typeVariableMap the TypeVariable Map to resolved against
188      * @return the type if it resolves to a Class, or <code>Object.class</code> otherwise
189      */
190     static Class resolveType(Type genericType, Map<TypeVariable, Type> typeVariableMap)
191     {
192         Type rawType = getRawType(genericType, typeVariableMap);
193         return (rawType instanceof Class ? (Class) rawType : Object.class);
194     }
195 
196     /**
197      * Determine the raw type for the given generic parameter type.
198      *
199      * @param genericType     the generic type to resolve
200      * @param typeVariableMap the TypeVariable Map to resolved against
201      * @return the resolved raw type
202      */
203     static Type getRawType(Type genericType, Map<TypeVariable, Type> typeVariableMap)
204     {
205         Type resolvedType = genericType;
206         if (genericType instanceof TypeVariable)
207         {
208             TypeVariable tv = (TypeVariable) genericType;
209             resolvedType = typeVariableMap.get(tv);
210             if (resolvedType == null)
211             {
212                 resolvedType = extractBoundForTypeVariable(tv);
213             }
214         }
215         if (resolvedType instanceof ParameterizedType)
216         {
217             return ((ParameterizedType) resolvedType).getRawType();
218         }
219         else
220         {
221             return resolvedType;
222         }
223     }
224 
225     /**
226      * Build a mapping of {@link TypeVariable#getName TypeVariable names} to concrete
227      * {@link Class} for the specified {@link Class}. Searches all super types,
228      * enclosing types and interfaces.
229      */
230     static Map<TypeVariable, Type> getTypeVariableMap(Class clazz)
231     {
232         Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz);
233         Map<TypeVariable, Type> typeVariableMap = (ref != null ? ref.get() : null);
234 
235         if (typeVariableMap == null)
236         {
237             typeVariableMap = new HashMap<TypeVariable, Type>();
238 
239             // interfaces
240             extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap);
241 
242             // super class
243             Type genericType = clazz.getGenericSuperclass();
244             Class type = clazz.getSuperclass();
245             while (type != null && !Object.class.equals(type))
246             {
247                 if (genericType instanceof ParameterizedType)
248                 {
249                     ParameterizedType pt = (ParameterizedType) genericType;
250                     populateTypeMapFromParameterizedType(pt, typeVariableMap);
251                 }
252                 extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), typeVariableMap);
253                 genericType = type.getGenericSuperclass();
254                 type = type.getSuperclass();
255             }
256 
257             // enclosing class
258             type = clazz;
259             while (type.isMemberClass())
260             {
261                 genericType = type.getGenericSuperclass();
262                 if (genericType instanceof ParameterizedType)
263                 {
264                     ParameterizedType pt = (ParameterizedType) genericType;
265                     populateTypeMapFromParameterizedType(pt, typeVariableMap);
266                 }
267                 type = type.getEnclosingClass();
268             }
269 
270             typeVariableCache.put(clazz, new WeakReference<Map<TypeVariable, Type>>(typeVariableMap));
271         }
272 
273         return typeVariableMap;
274     }
275 
276     /**
277      * Extracts the bound <code>Type</code> for a given {@link TypeVariable}.
278      */
279     static Type extractBoundForTypeVariable(TypeVariable typeVariable)
280     {
281         Type[] bounds = typeVariable.getBounds();
282         if (bounds.length == 0)
283         {
284             return Object.class;
285         }
286         Type bound = bounds[0];
287         if (bound instanceof TypeVariable)
288         {
289             bound = extractBoundForTypeVariable((TypeVariable) bound);
290         }
291         return bound;
292     }
293 
294     private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap)
295     {
296         for (Type genericInterface : genericInterfaces)
297         {
298             if (genericInterface instanceof ParameterizedType)
299             {
300                 ParameterizedType pt = (ParameterizedType) genericInterface;
301                 populateTypeMapFromParameterizedType(pt, typeVariableMap);
302                 if (pt.getRawType() instanceof Class)
303                 {
304                     extractTypeVariablesFromGenericInterfaces(
305                             ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap);
306                 }
307             }
308             else if (genericInterface instanceof Class)
309             {
310                 extractTypeVariablesFromGenericInterfaces(
311                         ((Class) genericInterface).getGenericInterfaces(), typeVariableMap);
312             }
313         }
314     }
315 
316     /**
317      * Read the {@link TypeVariable TypeVariables} from the supplied {@link ParameterizedType}
318      * and add mappings corresponding to the {@link TypeVariable#getName TypeVariable name} ->
319      * concrete type to the supplied {@link Map}.
320      * <p>Consider this case:
321      * <pre class="code>
322      * public interface Foo<S, T> {
323      * ..
324      * }
325      * <p/>
326      * public class FooImpl implements Foo<String, Integer> {
327      * ..
328      * }</pre>
329      * For '<code>FooImpl</code>' the following mappings would be added to the {@link Map}:
330      * {S=java.lang.String, T=java.lang.Integer}.
331      */
332     private static void populateTypeMapFromParameterizedType(ParameterizedType type, Map<TypeVariable, Type> typeVariableMap)
333     {
334         if (type.getRawType() instanceof Class)
335         {
336             Type[] actualTypeArguments = type.getActualTypeArguments();
337             TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters();
338             for (int i = 0; i < actualTypeArguments.length; i++)
339             {
340                 Type actualTypeArgument = actualTypeArguments[i];
341                 TypeVariable variable = typeVariables[i];
342                 if (actualTypeArgument instanceof Class)
343                 {
344                     typeVariableMap.put(variable, actualTypeArgument);
345                 }
346                 else if (actualTypeArgument instanceof GenericArrayType)
347                 {
348                     typeVariableMap.put(variable, actualTypeArgument);
349                 }
350                 else if (actualTypeArgument instanceof ParameterizedType)
351                 {
352                     typeVariableMap.put(variable, actualTypeArgument);
353                 }
354                 else if (actualTypeArgument instanceof TypeVariable)
355                 {
356                     // We have a type that is parameterized at instantiation time
357                     // the nearest match on the bridge method will be the bounded type.
358                     TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument;
359                     Type resolvedType = typeVariableMap.get(typeVariableArgument);
360                     if (resolvedType == null) {
361                         resolvedType = extractBoundForTypeVariable(typeVariableArgument);
362                     }
363                     typeVariableMap.put(variable, resolvedType);
364                 }
365             }
366         }
367     }
368 
369 }