net.jodah.typetools.TypeResolver.java Source code

Java tutorial

Introduction

Here is the source code for net.jodah.typetools.TypeResolver.java

Source

/**
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed 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 net.jodah.typetools;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import sun.reflect.ConstantPool;

/**
 * Enhanced type resolution utilities. Originally based on org.springframework.core.GenericTypeResolver.
 *
 * @author Jonathan Halterman
 */
@SuppressWarnings("restriction")
public final class TypeResolver {
    /** Cache of type variable/argument pairs */
    private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> typeVariableCache = Collections
            .synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>());
    private static volatile int METHOD_REF_OFFSET = -1;
    private static volatile boolean CACHE_ENABLED = true;
    private static boolean SUPPORTS_LAMBDAS;
    private static Method GET_CONSTANT_POOL;
    private static Map<String, Method> OBJECT_METHODS = new HashMap<String, Method>();

    static {
        SUPPORTS_LAMBDAS = Double.valueOf(System.getProperty("java.version").substring(0, 3)) >= 1.8;
        if (SUPPORTS_LAMBDAS) {
            for (Method method : Object.class.getDeclaredMethods())
                OBJECT_METHODS.put(method.getName(), method);

            try {
                GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool");
                GET_CONSTANT_POOL.setAccessible(true);
            } catch (Exception e) {
            }

            if (GET_CONSTANT_POOL == null)
                SUPPORTS_LAMBDAS = false;
        }
    }

    /** An unknown type. */
    public static final class Unknown {
        private Unknown() {
        }
    }

    private TypeResolver() {
    }

    /**
     * Enables the internal caching of resolved TypeVariables.
     */
    public static void enableCache() {
        CACHE_ENABLED = true;
    }

    /**
     * Disables the internal caching of resolved TypeVariables.
     */
    public static void disableCache() {
        typeVariableCache.clear();
        CACHE_ENABLED = false;
    }

    /**
     * Returns the raw class representing the argument for the {@code type} using type variable information from the
     * {@code subType}. If no arguments can be resolved then {@code Unknown.class} is returned.
     *
     * @param type to resolve argument for
     * @param subType to extract type variable information from
     * @return argument for {@code type} else {@link Unknown.class} if no type arguments are declared
     * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code type}
     */
    public static <T, S extends T> Class<?> resolveRawArgument(Class<T> type, Class<S> subType) {
        return resolveRawArgument(resolveGenericType(type, subType), subType);
    }

    /**
     * Returns the raw class representing the argument for the {@code genericType} using type variable information from
     * the {@code subType}. If {@code genericType} is an instance of class, then {@code genericType} is returned. If no
     * arguments can be resolved then {@code Unknown.class} is returned.
     *
     * @param genericType to resolve argument for
     * @param subType to extract type variable information from
     * @return argument for {@code genericType} else {@link Unknown.class} if no type arguments are declared
     * @throws IllegalArgumentException if more or less than one argument is resolved for the {@code genericType}
     */
    public static Class<?> resolveRawArgument(Type genericType, Class<?> subType) {
        Class<?>[] arguments = resolveRawArguments(genericType, subType);
        if (arguments == null)
            return Unknown.class;

        if (arguments.length != 1)
            throw new IllegalArgumentException(
                    "Expected 1 argument for generic type " + genericType + " but found " + arguments.length);

        return arguments[0];
    }

    /**
     * Returns an array of raw classes representing arguments for the {@code type} using type variable information from
     * the {@code subType}. Arguments for {@code type} that cannot be resolved are returned as {@code Unknown.class}. If
     * no arguments can be resolved then {@code null} is returned.
     *
     * @param type to resolve arguments for
     * @param subType to extract type variable information from
     * @return array of raw classes representing arguments for the {@code type} else {@code null} if no type arguments are
     *         declared
     */
    public static <T, S extends T> Class<?>[] resolveRawArguments(Class<T> type, Class<S> subType) {
        return resolveRawArguments(resolveGenericType(type, subType), subType);
    }

    /**
     * Returns an array of raw classes representing arguments for the {@code genericType} using type variable information
     * from the {@code subType}. Arguments for {@code genericType} that cannot be resolved are returned as
     * {@code Unknown.class}. If no arguments can be resolved then {@code null} is returned.
     *
     * @param genericType to resolve arguments for
     * @param subType to extract type variable information from
     * @return array of raw classes representing arguments for the {@code genericType} else {@code null} if no type
     *         arguments are declared
     */
    public static Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
        Class<?>[] result = null;
        Class<?> functionalInterface = null;

        // Handle lambdas
        if (SUPPORTS_LAMBDAS && subType.isSynthetic()) {
            Class<?> fi = genericType instanceof ParameterizedType
                    && ((ParameterizedType) genericType).getRawType() instanceof Class
                            ? (Class<?>) ((ParameterizedType) genericType).getRawType()
                            : genericType instanceof Class ? (Class<?>) genericType : null;
            if (fi != null && fi.isInterface())
                functionalInterface = fi;
        }

        if (genericType instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) genericType;
            Type[] arguments = paramType.getActualTypeArguments();
            result = new Class[arguments.length];
            for (int i = 0; i < arguments.length; i++)
                result[i] = resolveRawClass(arguments[i], subType, functionalInterface);
        } else if (genericType instanceof TypeVariable) {
            result = new Class[1];
            result[0] = resolveRawClass(genericType, subType, functionalInterface);
        } else if (genericType instanceof Class) {
            TypeVariable<?>[] typeParams = ((Class<?>) genericType).getTypeParameters();
            result = new Class[typeParams.length];
            for (int i = 0; i < typeParams.length; i++)
                result[i] = resolveRawClass(typeParams[i], subType, functionalInterface);
        }

        return result;
    }

    /**
     * Returns the generic {@code type} using type variable information from the {@code subType} else {@code null} if the
     * generic type cannot be resolved.
     *
     * @param type to resolve generic type for
     * @param subType to extract type variable information from
     * @return generic {@code type} else {@code null} if it cannot be resolved
     */
    public static Type resolveGenericType(Class<?> type, Type subType) {
        Class<?> rawType;
        if (subType instanceof ParameterizedType)
            rawType = (Class<?>) ((ParameterizedType) subType).getRawType();
        else
            rawType = (Class<?>) subType;

        if (type.equals(rawType))
            return subType;

        Type result;
        if (type.isInterface()) {
            for (Type superInterface : rawType.getGenericInterfaces())
                if (superInterface != null && !superInterface.equals(Object.class))
                    if ((result = resolveGenericType(type, superInterface)) != null)
                        return result;
        }

        Type superClass = rawType.getGenericSuperclass();
        if (superClass != null && !superClass.equals(Object.class))
            if ((result = resolveGenericType(type, superClass)) != null)
                return result;

        return null;
    }

    /**
     * Resolves the raw class for the {@code genericType}, using the type variable information from the {@code subType}
     * else {@link Unknown} if the raw class cannot be resolved.
     *
     * @param type to resolve raw class for
     * @param subType to extract type variable information from
     * @return raw class for the {@code genericType} else {@link Unknown} if it cannot be resolved
     */
    public static Class<?> resolveRawClass(Type genericType, Class<?> subType) {
        return resolveRawClass(genericType, subType, null);
    }

    private static Class<?> resolveRawClass(Type genericType, Class<?> subType, Class<?> functionalInterface) {
        if (genericType instanceof Class) {
            return (Class<?>) genericType;
        } else if (genericType instanceof ParameterizedType) {
            return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType, functionalInterface);
        } else if (genericType instanceof GenericArrayType) {
            GenericArrayType arrayType = (GenericArrayType) genericType;
            Class<?> compoment = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface);
            return Array.newInstance(compoment, 0).getClass();
        } else if (genericType instanceof TypeVariable) {
            TypeVariable<?> variable = (TypeVariable<?>) genericType;
            genericType = getTypeVariableMap(subType, functionalInterface).get(variable);
            genericType = genericType == null ? resolveBound(variable)
                    : resolveRawClass(genericType, subType, functionalInterface);
        }

        return genericType instanceof Class ? (Class<?>) genericType : Unknown.class;
    }

    private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType,
            Class<?> functionalInterface) {
        Reference<Map<TypeVariable<?>, Type>> ref = typeVariableCache.get(targetType);
        Map<TypeVariable<?>, Type> map = ref != null ? ref.get() : null;

        if (map == null) {
            map = new HashMap<TypeVariable<?>, Type>();

            // Populate lambdas
            if (functionalInterface != null)
                populateLambdaArgs(functionalInterface, targetType, map);

            // Populate interfaces
            populateSuperTypeArgs(targetType.getGenericInterfaces(), map, functionalInterface != null);

            // Populate super classes and interfaces
            Type genericType = targetType.getGenericSuperclass();
            Class<?> type = targetType.getSuperclass();
            while (type != null && !Object.class.equals(type)) {
                if (genericType instanceof ParameterizedType)
                    populateTypeArgs((ParameterizedType) genericType, map, false);
                populateSuperTypeArgs(type.getGenericInterfaces(), map, false);

                genericType = type.getGenericSuperclass();
                type = type.getSuperclass();
            }

            // Populate enclosing classes
            type = targetType;
            while (type.isMemberClass()) {
                genericType = type.getGenericSuperclass();
                if (genericType instanceof ParameterizedType)
                    populateTypeArgs((ParameterizedType) genericType, map, functionalInterface != null);

                type = type.getEnclosingClass();
            }

            if (CACHE_ENABLED)
                typeVariableCache.put(targetType, new WeakReference<Map<TypeVariable<?>, Type>>(map));
        }

        return map;
    }

    /**
     * Populates the {@code map} with variable/argument pairs for the {@code functionalInterface}.
     */
    private static void populateLambdaArgs(Class<?> functionalInterface, final Class<?> lambdaType,
            Map<TypeVariable<?>, Type> map) {
        if (GET_CONSTANT_POOL != null) {
            try {
                // Find SAM
                for (Method m : functionalInterface.getMethods()) {
                    if (!m.isDefault() && !Modifier.isStatic(m.getModifiers()) && !m.isBridge()) {
                        // Skip methods that override Object.class
                        Method objectMethod = OBJECT_METHODS.get(m.getName());
                        if (objectMethod != null
                                && Arrays.equals(m.getTypeParameters(), objectMethod.getTypeParameters()))
                            continue;

                        // Get functional interface's type params
                        Type returnTypeVar = m.getGenericReturnType();
                        Type[] paramTypeVars = m.getGenericParameterTypes();

                        // Get lambda's type arguments
                        ConstantPool constantPool = (ConstantPool) GET_CONSTANT_POOL.invoke(lambdaType);
                        String[] methodRefInfo = constantPool.getMemberRefInfoAt(
                                constantPool.getSize() - resolveMethodRefOffset(constantPool, lambdaType));

                        // Skip auto boxing methods
                        if (methodRefInfo[1].equals("valueOf") && constantPool.getSize() > 22) {
                            try {
                                methodRefInfo = constantPool.getMemberRefInfoAt(constantPool.getSize()
                                        - resolveAutoboxedMethodRefOffset(constantPool, lambdaType));
                            } catch (MethodRefOffsetResolutionFailed ignore) {
                            }
                        }

                        if (returnTypeVar instanceof TypeVariable) {
                            Class<?> returnType = TypeDescriptor.getReturnType(methodRefInfo[2])
                                    .getType(lambdaType.getClassLoader());
                            if (!returnType.equals(Void.class))
                                map.put((TypeVariable<?>) returnTypeVar, returnType);
                        }

                        TypeDescriptor[] arguments = TypeDescriptor.getArgumentTypes(methodRefInfo[2]);

                        // Handle arbitrary object instance method references
                        int paramOffset = 0;
                        if (paramTypeVars[0] instanceof TypeVariable
                                && paramTypeVars.length == arguments.length + 1) {
                            Class<?> instanceType = TypeDescriptor.getObjectType(methodRefInfo[0])
                                    .getType(lambdaType.getClassLoader());
                            map.put((TypeVariable<?>) paramTypeVars[0], instanceType);
                            paramOffset = 1;
                        }

                        // Handle local final variables from context that are passed as arguments.
                        int argOffset = 0;
                        if (paramTypeVars.length < arguments.length) {
                            argOffset = arguments.length - paramTypeVars.length;
                        }

                        for (int i = 0; i + argOffset < arguments.length; i++) {
                            if (paramTypeVars[i] instanceof TypeVariable) {
                                map.put((TypeVariable<?>) paramTypeVars[i + paramOffset],
                                        arguments[i + argOffset].getType(lambdaType.getClassLoader()));
                            }
                        }
                        break;
                    }
                }

            } catch (Exception ignore) {
            }
        }
    }

    private static final class MethodRefOffsetResolutionFailed extends RuntimeException {
    }

    /**
     * Populates the {@code map} with with variable/argument pairs for the given {@code types}.
     */
    private static void populateSuperTypeArgs(final Type[] types, final Map<TypeVariable<?>, Type> map,
            boolean depthFirst) {
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                if (!depthFirst)
                    populateTypeArgs(parameterizedType, map, depthFirst);
                Type rawType = parameterizedType.getRawType();
                if (rawType instanceof Class)
                    populateSuperTypeArgs(((Class<?>) rawType).getGenericInterfaces(), map, depthFirst);
                if (depthFirst)
                    populateTypeArgs(parameterizedType, map, depthFirst);
            } else if (type instanceof Class) {
                populateSuperTypeArgs(((Class<?>) type).getGenericInterfaces(), map, depthFirst);
            }
        }
    }

    /**
     * Populates the {@code map} with variable/argument pairs for the given {@code type}.
     */
    private static void populateTypeArgs(ParameterizedType type, Map<TypeVariable<?>, Type> map,
            boolean depthFirst) {
        if (type.getRawType() instanceof Class) {
            TypeVariable<?>[] typeVariables = ((Class<?>) type.getRawType()).getTypeParameters();
            Type[] typeArguments = type.getActualTypeArguments();

            if (type.getOwnerType() != null) {
                Type owner = type.getOwnerType();
                if (owner instanceof ParameterizedType)
                    populateTypeArgs((ParameterizedType) owner, map, depthFirst);
            }

            for (int i = 0; i < typeArguments.length; i++) {
                TypeVariable<?> variable = typeVariables[i];
                Type typeArgument = typeArguments[i];

                if (typeArgument instanceof Class) {
                    map.put(variable, typeArgument);
                } else if (typeArgument instanceof GenericArrayType) {
                    map.put(variable, typeArgument);
                } else if (typeArgument instanceof ParameterizedType) {
                    map.put(variable, typeArgument);
                } else if (typeArgument instanceof TypeVariable) {
                    TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument;
                    if (depthFirst) {
                        Type existingType = map.get(variable);
                        if (existingType != null) {
                            map.put(typeVariableArgument, existingType);
                            continue;
                        }
                    }

                    Type resolvedType = map.get(typeVariableArgument);
                    if (resolvedType == null)
                        resolvedType = resolveBound(typeVariableArgument);
                    map.put(variable, resolvedType);
                }
            }
        }
    }

    /**
     * Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved.
     */
    public static Type resolveBound(TypeVariable<?> typeVariable) {
        Type[] bounds = typeVariable.getBounds();
        if (bounds.length == 0)
            return Unknown.class;

        Type bound = bounds[0];
        if (bound instanceof TypeVariable)
            bound = resolveBound((TypeVariable<?>) bound);

        return bound == Object.class ? Unknown.class : bound;
    }

    /**
     * Resolves method ref offset.
     */
    private static int resolveMethodRefOffset(ConstantPool constantPool, Class<?> lambdaType) {
        int offset = METHOD_REF_OFFSET;

        if (offset == -1) {
            int constantPoolSize = constantPool.getSize();

            for (int i = 0; i < constantPoolSize; i++) {
                try {
                    constantPool.getMemberRefInfoAt(constantPoolSize - i);
                    offset = i;
                    break;
                } catch (IllegalArgumentException ignore) {
                }
            }

            METHOD_REF_OFFSET = offset;
        }

        if (offset >= 0)
            return offset;
        else
            throw new MethodRefOffsetResolutionFailed();
    }

    /**
     * Resolves method ref offset of method reference lambda type having autoboxed return type. We can't cache the result
     * value because results are different depending on given {@code lambdaType}.
     */
    private static int resolveAutoboxedMethodRefOffset(ConstantPool constantPool, Class<?> lambdaType) {
        int constantPoolSize = constantPool.getSize();

        for (int i = resolveMethodRefOffset(constantPool, lambdaType) + 1; i < constantPoolSize; i++) {
            try {
                // ignore constructor
                if (!constantPool.getMemberRefInfoAt(constantPoolSize - i)[1].equals("<init>"))
                    return i;
            } catch (IllegalArgumentException ignore) {
            }
        }

        throw new MethodRefOffsetResolutionFailed();
    }
}