Coverage Report - org.mule.util.generics.GenericTypeResolver
 
Classes in this File Line Coverage Branch Coverage Complexity
GenericTypeResolver
0%
0/119
0%
0/78
0
 
 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  0
     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  0
     {
 41  
         // do not isntantiate
 42  0
     }
 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  0
         if (methodParam.getConstructor() != null)
 53  
         {
 54  0
             return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()];
 55  
         }
 56  
         else
 57  
         {
 58  0
             if (methodParam.getParameterIndex() >= 0)
 59  
             {
 60  0
                 return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()];
 61  
             }
 62  
             else
 63  
             {
 64  0
                 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  0
         Type genericType = getTargetType(methodParam);
 79  0
         Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
 80  0
         Type rawType = getRawType(genericType, typeVariableMap);
 81  0
         Class result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType());
 82  0
         methodParam.setParameterType(result);
 83  0
         methodParam.typeVariableMap = typeVariableMap;
 84  0
         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  0
         Type genericType = method.getGenericReturnType();
 97  0
         Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
 98  0
         Type rawType = getRawType(genericType, typeVariableMap);
 99  0
         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  0
         Class[] typeArgs = resolveTypeArguments(clazz, genericIfc);
 114  0
         if (typeArgs == null)
 115  
         {
 116  0
             return null;
 117  
         }
 118  0
         if (typeArgs.length != 1)
 119  
         {
 120  0
             throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
 121  
                     genericIfc.getName() + "] but found " + typeArgs.length);
 122  
         }
 123  0
         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  0
         return doResolveTypeArguments(clazz, clazz, genericIfc);
 139  
     }
 140  
 
 141  
     private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc)
 142  
     {
 143  0
         while (classToIntrospect != null)
 144  
         {
 145  0
             Type[] ifcs = classToIntrospect.getGenericInterfaces();
 146  0
             for (Type ifc : ifcs)
 147  
             {
 148  0
                 if (ifc instanceof ParameterizedType)
 149  
                 {
 150  0
                     ParameterizedType paramIfc = (ParameterizedType) ifc;
 151  0
                     Type rawType = paramIfc.getRawType();
 152  0
                     if (genericIfc.equals(rawType))
 153  
                     {
 154  0
                         Type[] typeArgs = paramIfc.getActualTypeArguments();
 155  0
                         Class[] result = new Class[typeArgs.length];
 156  0
                         for (int i = 0; i < typeArgs.length; i++)
 157  
                         {
 158  0
                             Type arg = typeArgs[i];
 159  0
                             if (arg instanceof TypeVariable)
 160  
                             {
 161  0
                                 arg = getTypeVariableMap(ownerClass).get((TypeVariable) arg);
 162  
                             }
 163  0
                             result[i] = (arg instanceof Class ? (Class) arg : Object.class);
 164  
                         }
 165  0
                         return result;
 166  
                     }
 167  0
                     else if (genericIfc.isAssignableFrom((Class) rawType))
 168  
                     {
 169  0
                         return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc);
 170  
                     }
 171  0
                 }
 172  0
                 else if (genericIfc.isAssignableFrom((Class) ifc))
 173  
                 {
 174  0
                     return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc);
 175  
                 }
 176  
             }
 177  0
             classToIntrospect = classToIntrospect.getSuperclass();
 178  0
         }
 179  0
         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  0
         Type rawType = getRawType(genericType, typeVariableMap);
 193  0
         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  0
         Type resolvedType = genericType;
 206  0
         if (genericType instanceof TypeVariable)
 207  
         {
 208  0
             TypeVariable tv = (TypeVariable) genericType;
 209  0
             resolvedType = typeVariableMap.get(tv);
 210  0
             if (resolvedType == null)
 211  
             {
 212  0
                 resolvedType = extractBoundForTypeVariable(tv);
 213  
             }
 214  
         }
 215  0
         if (resolvedType instanceof ParameterizedType)
 216  
         {
 217  0
             return ((ParameterizedType) resolvedType).getRawType();
 218  
         }
 219  
         else
 220  
         {
 221  0
             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  0
         Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz);
 233  0
         Map<TypeVariable, Type> typeVariableMap = (ref != null ? ref.get() : null);
 234  
 
 235  0
         if (typeVariableMap == null)
 236  
         {
 237  0
             typeVariableMap = new HashMap<TypeVariable, Type>();
 238  
 
 239  
             // interfaces
 240  0
             extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap);
 241  
 
 242  
             // super class
 243  0
             Type genericType = clazz.getGenericSuperclass();
 244  0
             Class type = clazz.getSuperclass();
 245  0
             while (type != null && !Object.class.equals(type))
 246  
             {
 247  0
                 if (genericType instanceof ParameterizedType)
 248  
                 {
 249  0
                     ParameterizedType pt = (ParameterizedType) genericType;
 250  0
                     populateTypeMapFromParameterizedType(pt, typeVariableMap);
 251  
                 }
 252  0
                 extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), typeVariableMap);
 253  0
                 genericType = type.getGenericSuperclass();
 254  0
                 type = type.getSuperclass();
 255  
             }
 256  
 
 257  
             // enclosing class
 258  0
             type = clazz;
 259  0
             while (type.isMemberClass())
 260  
             {
 261  0
                 genericType = type.getGenericSuperclass();
 262  0
                 if (genericType instanceof ParameterizedType)
 263  
                 {
 264  0
                     ParameterizedType pt = (ParameterizedType) genericType;
 265  0
                     populateTypeMapFromParameterizedType(pt, typeVariableMap);
 266  
                 }
 267  0
                 type = type.getEnclosingClass();
 268  
             }
 269  
 
 270  0
             typeVariableCache.put(clazz, new WeakReference<Map<TypeVariable, Type>>(typeVariableMap));
 271  
         }
 272  
 
 273  0
         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  0
         Type[] bounds = typeVariable.getBounds();
 282  0
         if (bounds.length == 0)
 283  
         {
 284  0
             return Object.class;
 285  
         }
 286  0
         Type bound = bounds[0];
 287  0
         if (bound instanceof TypeVariable)
 288  
         {
 289  0
             bound = extractBoundForTypeVariable((TypeVariable) bound);
 290  
         }
 291  0
         return bound;
 292  
     }
 293  
 
 294  
     private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap)
 295  
     {
 296  0
         for (Type genericInterface : genericInterfaces)
 297  
         {
 298  0
             if (genericInterface instanceof ParameterizedType)
 299  
             {
 300  0
                 ParameterizedType pt = (ParameterizedType) genericInterface;
 301  0
                 populateTypeMapFromParameterizedType(pt, typeVariableMap);
 302  0
                 if (pt.getRawType() instanceof Class)
 303  
                 {
 304  0
                     extractTypeVariablesFromGenericInterfaces(
 305  
                             ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap);
 306  
                 }
 307  0
             }
 308  0
             else if (genericInterface instanceof Class)
 309  
             {
 310  0
                 extractTypeVariablesFromGenericInterfaces(
 311  
                         ((Class) genericInterface).getGenericInterfaces(), typeVariableMap);
 312  
             }
 313  
         }
 314  0
     }
 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  0
         if (type.getRawType() instanceof Class)
 335  
         {
 336  0
             Type[] actualTypeArguments = type.getActualTypeArguments();
 337  0
             TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters();
 338  0
             for (int i = 0; i < actualTypeArguments.length; i++)
 339  
             {
 340  0
                 Type actualTypeArgument = actualTypeArguments[i];
 341  0
                 TypeVariable variable = typeVariables[i];
 342  0
                 if (actualTypeArgument instanceof Class)
 343  
                 {
 344  0
                     typeVariableMap.put(variable, actualTypeArgument);
 345  
                 }
 346  0
                 else if (actualTypeArgument instanceof GenericArrayType)
 347  
                 {
 348  0
                     typeVariableMap.put(variable, actualTypeArgument);
 349  
                 }
 350  0
                 else if (actualTypeArgument instanceof ParameterizedType)
 351  
                 {
 352  0
                     typeVariableMap.put(variable, actualTypeArgument);
 353  
                 }
 354  0
                 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  0
                     TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument;
 359  0
                     Type resolvedType = typeVariableMap.get(typeVariableArgument);
 360  0
                     if (resolvedType == null) {
 361  0
                         resolvedType = extractBoundForTypeVariable(typeVariableArgument);
 362  
                     }
 363  0
                     typeVariableMap.put(variable, resolvedType);
 364  
                 }
 365  
             }
 366  
         }
 367  0
     }
 368  
 
 369  
 }