content:  /*
   *  Copyright Beijing 58 Information Technology Co.,Ltd.
   *
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *        http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   */
   package com.bj58.argo.thirdparty;
  
   import com.google.common.base.Preconditions;
  
   import java.lang.ref.Reference;
   import java.lang.ref.WeakReference;
   import java.lang.reflect.*;
   import java.util.Collections;
   import java.util.HashMap;
   import java.util.Map;
   import java.util.WeakHashMap;
  
  
   /**
    * 型别解析工具类
    *
    * @author Service Platform Architecture Team (spat@58.com)
    */
   public abstract class GenericTypeResolver {
  
       /** Cache from Class to TypeVariable Map */
       private static final Map<Class, Reference<Map<TypeVariable, Type>>> typeVariableCache =
               Collections.synchronizedMap(new WeakHashMap<Class, Reference<Map<TypeVariable, Type>>>());
  
  
   //	/**
   //	 * Determine the target type for the given parameter specification.
   //	 * @param methodParam the method parameter specification
   //	 * @return the corresponding generic parameter type
   //	 */
   //	public static Type getTargetType(MethodParameter methodParam) {
   //		Assert.notNull(methodParam, "MethodParameter must not be null");
   //		if (methodParam.getConstructor() != null) {
   //			return methodParam.getConstructor().getGenericParameterTypes()[methodParam.getParameterIndex()];
   //		}
   //		else {
   //			if (methodParam.getParameterIndex() >= 0) {
   //				return methodParam.getMethod().getGenericParameterTypes()[methodParam.getParameterIndex()];
   //			}
   //			else {
   //				return methodParam.getMethod().getGenericReturnType();
   //			}
   //		}
   //	}
  
   //	/**
   //	 * Determine the target type for the given generic parameter type.
   //	 * @param methodParam the method parameter specification
   //	 * @param clazz the class to resolve type variables against
   //	 * @return the corresponding generic parameter or return type
   //	 */
   //	public static Class<?> resolveParameterType(MethodParameter methodParam, Class clazz) {
   //		Type genericType = getTargetType(methodParam);
   //		Assert.notNull(clazz, "Class must not be null");
   //		Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
   //		Type rawType = getRawType(genericType, typeVariableMap);
   //		Class result = (rawType instanceof Class ? (Class) rawType : methodParam.getParameterType());
   //		methodParam.setParameterType(result);
   //		methodParam.typeVariableMap = typeVariableMap;
   //		return result;
   //	}
  
       /**
        * Determine the target type for the generic return type of the given method.
        * @param method the method to introspect
        * @param clazz the class to resolve type variables against
        * @return the corresponding generic parameter or return type
        */
       public static Class<?> resolveReturnType(Method method, Class clazz) {
           Preconditions.checkNotNull(method, "Method must not be null");
           Type genericType = method.getGenericReturnType();
           Preconditions.checkNotNull(clazz, "Class must not be null");
           Map<TypeVariable, Type> typeVariableMap = getTypeVariableMap(clazz);
           Type rawType = getRawType(genericType, typeVariableMap);
           return (rawType instanceof Class ? (Class) rawType : method.getReturnType());
       }
  
       /**
        * Resolve the single type argument of the given generic interface against
        * the given target class which is assumed to implement the generic interface
        * and possibly declare a concrete type for its type variable.
        * @param clazz the target class to check against
        * @param genericIfc the generic interface or superclass to resolve the type argument from
        * @return the resolved type of the argument, or <code>null</code> if not resolvable
        */
       public static Class<?> resolveTypeArgument(Class clazz, Class genericIfc) {
           Class[] typeArgs = resolveTypeArguments(clazz, genericIfc);
           if (typeArgs == null) {
               return null;
           }
           if (typeArgs.length != 1) {
               throw new IllegalArgumentException("Expected 1 type argument on generic interface [" +
                       genericIfc.getName() + "] but found " + typeArgs.length);
           }
           return typeArgs[0];
       }
  
       /**
        * Resolve the type arguments of the given generic interface against the given
        * target class which is assumed to implement the generic interface and possibly
        * declare concrete types for its type variables.
        * @param clazz the target class to check against
        * @param genericIfc the generic interface or superclass to resolve the type argument from
        * @return the resolved type of each argument, with the array size matching the
        * number of actual type arguments, or <code>null</code> if not resolvable
        */
       public static Class[] resolveTypeArguments(Class clazz, Class genericIfc) {
           return doResolveTypeArguments(clazz, clazz, genericIfc);
       }
  
       private static Class[] doResolveTypeArguments(Class ownerClass, Class classToIntrospect, Class genericIfc) {
           while (classToIntrospect != null) {
               if (genericIfc.isInterface()) {
                   Type[] ifcs = classToIntrospect.getGenericInterfaces();
                   for (Type ifc : ifcs) {
                       Class[] result = doResolveTypeArguments(ownerClass, ifc, genericIfc);
                       if (result != null) {
                           return result;
                       }
                   }
               }
               else {
                   Class[] result = doResolveTypeArguments(
                           ownerClass, classToIntrospect.getGenericSuperclass(), genericIfc);
                   if (result != null) {
                       return result;
                   }
               }
               classToIntrospect = classToIntrospect.getSuperclass();
           }
           return null;
       }
  
       private static Class[] doResolveTypeArguments(Class ownerClass, Type ifc, Class genericIfc) {
           if (ifc instanceof ParameterizedType) {
               ParameterizedType paramIfc = (ParameterizedType) ifc;
               Type rawType = paramIfc.getRawType();
               if (genericIfc.equals(rawType)) {
                   Type[] typeArgs = paramIfc.getActualTypeArguments();
                   Class[] result = new Class[typeArgs.length];
                   for (int i = 0; i < typeArgs.length; i++) {
                       Type arg = typeArgs[i];
                       result[i] = extractClass(ownerClass, arg);
                   }
                   return result;
               }
               else if (genericIfc.isAssignableFrom((Class) rawType)) {
                   return doResolveTypeArguments(ownerClass, (Class) rawType, genericIfc);
               }
           }
           else if (genericIfc.isAssignableFrom((Class) ifc)) {
               return doResolveTypeArguments(ownerClass, (Class) ifc, genericIfc);
           }
           return null;
       }
  
       /**
        * Extract a class instance from given Type.
        */
       private static Class extractClass(Class ownerClass, Type arg) {
           if (arg instanceof ParameterizedType) {
               return extractClass(ownerClass, ((ParameterizedType) arg).getRawType());
           }
           else if (arg instanceof GenericArrayType) {
               GenericArrayType gat = (GenericArrayType) arg;
               Type gt = gat.getGenericComponentType();
               Class<?> componentClass = extractClass(ownerClass, gt);
               return Array.newInstance(componentClass, 0).getClass();
           }
           else if (arg instanceof TypeVariable) {
               TypeVariable tv = (TypeVariable) arg;
               arg = getTypeVariableMap(ownerClass).get(tv);
               if (arg == null) {
                   arg = extractBoundForTypeVariable(tv);
               }
               else {
                   arg = extractClass(ownerClass, arg);
               }
           }
           return (arg instanceof Class ? (Class) arg : Object.class);
       }
  
  
       /**
        * Resolve the specified generic type against the given TypeVariable map.
        * @param genericType the generic type to resolve
        * @param typeVariableMap the TypeVariable Map to resolved against
        * @return the type if it resolves to a Class, or <code>Object.class</code> otherwise
        */
       static Class resolveType(Type genericType, Map<TypeVariable, Type> typeVariableMap) {
           Type rawType = getRawType(genericType, typeVariableMap);
           return (rawType instanceof Class ? (Class) rawType : Object.class);
       }
  
       /**
        * Determine the raw type for the given generic parameter type.
        * @param genericType the generic type to resolve
        * @param typeVariableMap the TypeVariable Map to resolved against
        * @return the resolved raw type
        */
       static Type getRawType(Type genericType, Map<TypeVariable, Type> typeVariableMap) {
           Type resolvedType = genericType;
           if (genericType instanceof TypeVariable) {
               TypeVariable tv = (TypeVariable) genericType;
               resolvedType = typeVariableMap.get(tv);
               if (resolvedType == null) {
                   resolvedType = extractBoundForTypeVariable(tv);
               }
           }
           if (resolvedType instanceof ParameterizedType) {
               return ((ParameterizedType) resolvedType).getRawType();
           }
           else {
               return resolvedType;
           }
       }
  
       /**
        * Build a mapping of {@link java.lang.reflect.TypeVariable#getName TypeVariable names} to concrete
        * {@link Class} for the specified {@link Class}. Searches all super types,
        * enclosing types and interfaces.
        */
       static Map<TypeVariable, Type> getTypeVariableMap(Class clazz) {
           Reference<Map<TypeVariable, Type>> ref = typeVariableCache.get(clazz);
           Map<TypeVariable, Type> typeVariableMap = (ref != null ? ref.get() : null);
  
           if (typeVariableMap == null) {
               typeVariableMap = new HashMap<TypeVariable, Type>();
  
               // interfaces
               extractTypeVariablesFromGenericInterfaces(clazz.getGenericInterfaces(), typeVariableMap);
  
               // super class
               Type genericType = clazz.getGenericSuperclass();
               Class type = clazz.getSuperclass();
               while (type != null && !Object.class.equals(type)) {
                   if (genericType instanceof ParameterizedType) {
                       ParameterizedType pt = (ParameterizedType) genericType;
                       populateTypeMapFromParameterizedType(pt, typeVariableMap);
                   }
                   extractTypeVariablesFromGenericInterfaces(type.getGenericInterfaces(), typeVariableMap);
                   genericType = type.getGenericSuperclass();
                   type = type.getSuperclass();
               }
  
               // enclosing class
               type = clazz;
               while (type.isMemberClass()) {
                   genericType = type.getGenericSuperclass();
                   if (genericType instanceof ParameterizedType) {
                       ParameterizedType pt = (ParameterizedType) genericType;
                       populateTypeMapFromParameterizedType(pt, typeVariableMap);
                   }
                   type = type.getEnclosingClass();
               }
  
               typeVariableCache.put(clazz, new WeakReference<Map<TypeVariable, Type>>(typeVariableMap));
           }
  
           return typeVariableMap;
       }
  
       /**
        * Extracts the bound <code>Type</code> for a given {@link java.lang.reflect.TypeVariable}.
        */
       static Type extractBoundForTypeVariable(TypeVariable typeVariable) {
           Type[] bounds = typeVariable.getBounds();
           if (bounds.length == 0) {
               return Object.class;
           }
           Type bound = bounds[0];
           if (bound instanceof TypeVariable) {
               bound = extractBoundForTypeVariable((TypeVariable) bound);
           }
           return bound;
       }
  
       private static void extractTypeVariablesFromGenericInterfaces(Type[] genericInterfaces, Map<TypeVariable, Type> typeVariableMap) {
           for (Type genericInterface : genericInterfaces) {
               if (genericInterface instanceof ParameterizedType) {
                   ParameterizedType pt = (ParameterizedType) genericInterface;
                   populateTypeMapFromParameterizedType(pt, typeVariableMap);
                   if (pt.getRawType() instanceof Class) {
                       extractTypeVariablesFromGenericInterfaces(
                               ((Class) pt.getRawType()).getGenericInterfaces(), typeVariableMap);
                   }
               }
               else if (genericInterface instanceof Class) {
                   extractTypeVariablesFromGenericInterfaces(
                           ((Class) genericInterface).getGenericInterfaces(), typeVariableMap);
               }
           }
       }
  
       /**
        * Read the {@link java.lang.reflect.TypeVariable TypeVariables} from the supplied {@link java.lang.reflect.ParameterizedType}
        * and add mappings corresponding to the {@link java.lang.reflect.TypeVariable#getName TypeVariable name} ->
        * concrete type to the supplied {@link java.util.Map}.
        * <p>Consider this case:
        * <pre class="code>
        * public interface Foo<S, T> {
        *  ..
        * }
        *
        * public class FooImpl implements Foo<String, Integer> {
        *  ..
        * }</pre>
        * For '<code>FooImpl</code>' the following mappings would be added to the {@link java.util.Map}:
        * {S=java.lang.String, T=java.lang.Integer}.
        */
       private static void populateTypeMapFromParameterizedType(ParameterizedType type, Map<TypeVariable, Type> typeVariableMap) {
           if (type.getRawType() instanceof Class) {
               Type[] actualTypeArguments = type.getActualTypeArguments();
               TypeVariable[] typeVariables = ((Class) type.getRawType()).getTypeParameters();
               for (int i = 0; i < actualTypeArguments.length; i++) {
                   Type actualTypeArgument = actualTypeArguments[i];
                   TypeVariable variable = typeVariables[i];
                   if (actualTypeArgument instanceof Class) {
                       typeVariableMap.put(variable, actualTypeArgument);
                   }
                   else if (actualTypeArgument instanceof GenericArrayType) {
                       typeVariableMap.put(variable, actualTypeArgument);
                   }
                   else if (actualTypeArgument instanceof ParameterizedType) {
                       typeVariableMap.put(variable, actualTypeArgument);
                   }
                   else if (actualTypeArgument instanceof TypeVariable) {
                       // We have a type that is parameterized at instantiation time
                       // the nearest match on the bridge method will be the bounded type.
                       TypeVariable typeVariableArgument = (TypeVariable) actualTypeArgument;
                       Type resolvedType = typeVariableMap.get(typeVariableArgument);
                       if (resolvedType == null) {
                           resolvedType = extractBoundForTypeVariable(typeVariableArgument);
                       }
                       typeVariableMap.put(variable, resolvedType);
                   }
               }
           }
       }
  
   }
  