View Javadoc

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