Added support for getting generic type information from lambda
expressions
This commit is contained in:
parent
f64118bae1
commit
f009f1223f
@ -15,39 +15,42 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.util;
|
package dorkbox.util;
|
||||||
|
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
public final
|
public final
|
||||||
class ClassHelper {
|
class ClassHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the generic type parameter for the PARENT (super) class of the specified class. This ONLY works
|
* Retrieves the generic type parameter for the PARENT (super) class of the specified class or lambda expression.
|
||||||
* on parent classes because of how type erasure works in java!
|
|
||||||
*
|
*
|
||||||
* @param clazz class to get the parameter from
|
* Because of how type erasure works in java, this will work on lambda expressions and ONLY parent/super classes.
|
||||||
|
*
|
||||||
|
* @param clazz class that defines what the parameters can be
|
||||||
|
* @param subClazz class to actually get the parameter from
|
||||||
* @param genericParameterToGet 0-based index of parameter as class to get
|
* @param genericParameterToGet 0-based index of parameter as class to get
|
||||||
|
*
|
||||||
|
* @return null if the generic type could not be found.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"StatementWithEmptyBody", "UnnecessaryLocalVariable"})
|
@SuppressWarnings({"StatementWithEmptyBody", "UnnecessaryLocalVariable"})
|
||||||
public static
|
public static
|
||||||
Class<?> getGenericParameterAsClassForSuperClass(Class<?> clazz, int genericParameterToGet) {
|
Class<?> getGenericParameterAsClassForSuperClass(Class<?> clazz, Class<?> subClazz, int genericParameterToGet) {
|
||||||
Class<?> classToCheck = clazz;
|
Class<?> classToCheck = subClazz;
|
||||||
|
|
||||||
|
// this will ALWAYS return something, if it is unknown, it will return TypeResolver.Unknown.class
|
||||||
|
Class<?>[] classes = TypeResolver.resolveRawArguments(clazz, classToCheck);
|
||||||
|
if (classes.length > genericParameterToGet && classes[genericParameterToGet] != TypeResolver.Unknown.class) {
|
||||||
|
return classes[genericParameterToGet];
|
||||||
|
}
|
||||||
|
|
||||||
// case of multiple inheritance, we are trying to get the first available generic info
|
// case of multiple inheritance, we are trying to get the first available generic info
|
||||||
// don't check for Object.class (this is where superclass is null)
|
// don't check for Object.class (this is where superclass is null)
|
||||||
while (classToCheck != Object.class) {
|
while (classToCheck != Object.class) {
|
||||||
// check to see if we have what we are looking for on our CURRENT class
|
// check to see if we have what we are looking for on our CURRENT class
|
||||||
Type superClassGeneric = classToCheck.getGenericSuperclass();
|
Type superClassGeneric = classToCheck.getGenericSuperclass();
|
||||||
if (superClassGeneric instanceof ParameterizedType) {
|
|
||||||
Type[] actualTypeArguments = ((ParameterizedType) superClassGeneric).getActualTypeArguments();
|
|
||||||
// is it possible?
|
|
||||||
if (actualTypeArguments.length > genericParameterToGet) {
|
|
||||||
Class<?> rawTypeAsClass = getRawTypeAsClass(actualTypeArguments[genericParameterToGet]);
|
|
||||||
return rawTypeAsClass;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// record the parameters.
|
|
||||||
|
|
||||||
}
|
classes = TypeResolver.resolveRawArguments(superClassGeneric, classToCheck);
|
||||||
|
if (classes.length > genericParameterToGet && classes[genericParameterToGet] != TypeResolver.Unknown.class) {
|
||||||
|
return classes[genericParameterToGet];
|
||||||
}
|
}
|
||||||
|
|
||||||
// NO MATCH, so walk up.
|
// NO MATCH, so walk up.
|
||||||
@ -55,21 +58,15 @@ class ClassHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTHING! now check interfaces!
|
// NOTHING! now check interfaces!
|
||||||
classToCheck = clazz;
|
classToCheck = subClazz;
|
||||||
while (classToCheck != Object.class) {
|
while (classToCheck != Object.class) {
|
||||||
// check to see if we have what we are looking for on our CURRENT class interfaces
|
// check to see if we have what we are looking for on our CURRENT class interfaces
|
||||||
Type[] genericInterfaces = classToCheck.getGenericInterfaces();
|
Type[] genericInterfaces = classToCheck.getGenericInterfaces();
|
||||||
for (Type genericInterface : genericInterfaces) {
|
for (Type genericInterface : genericInterfaces) {
|
||||||
if (genericInterface instanceof ParameterizedType) {
|
|
||||||
Type[] actualTypeArguments = ((ParameterizedType) genericInterface).getActualTypeArguments();
|
classes = TypeResolver.resolveRawArguments(genericInterface, classToCheck);
|
||||||
// is it possible?
|
if (classes.length > genericParameterToGet && classes[genericParameterToGet] != TypeResolver.Unknown.class) {
|
||||||
if (actualTypeArguments.length > genericParameterToGet) {
|
return classes[genericParameterToGet];
|
||||||
Class<?> rawTypeAsClass = ClassHelper.getRawTypeAsClass(actualTypeArguments[genericParameterToGet]);
|
|
||||||
return rawTypeAsClass;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// record the parameters.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,76 +75,30 @@ class ClassHelper {
|
|||||||
classToCheck = classToCheck.getSuperclass();
|
classToCheck = classToCheck.getSuperclass();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// couldn't find it.
|
// couldn't find it.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the class that is this type.
|
* Check to see if clazz or interface directly has one of the interfaces defined by requiredClass
|
||||||
*/
|
|
||||||
@SuppressWarnings("UnnecessaryLocalVariable")
|
|
||||||
public static
|
|
||||||
Class<?> getRawTypeAsClass(Type type) {
|
|
||||||
if (type instanceof Class) {
|
|
||||||
Class<?> class1 = (Class<?>) type;
|
|
||||||
|
|
||||||
// if (class1.isArray()) {
|
|
||||||
// System.err.println("CLASS IS ARRAY TYPE: SHOULD WE DO ANYTHING WITH IT? " + class1.getSimpleName());
|
|
||||||
// return class1.getComponentType();
|
|
||||||
// } else {
|
|
||||||
return class1;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
else if (type instanceof GenericArrayType) {
|
|
||||||
// note: cannot have primitive types here, only objects that are arrays (byte[], Integer[], etc)
|
|
||||||
Type type2 = ((GenericArrayType) type).getGenericComponentType();
|
|
||||||
Class<?> rawType = getRawTypeAsClass(type2);
|
|
||||||
|
|
||||||
return Array.newInstance(rawType, 0)
|
|
||||||
.getClass();
|
|
||||||
}
|
|
||||||
else if (type instanceof ParameterizedType) {
|
|
||||||
// we cannot use parameterized types, because java can't go between classes and ptypes - and this
|
|
||||||
// going "in-between" is the magic -- and value -- of this entire infrastructure.
|
|
||||||
// return the type.
|
|
||||||
|
|
||||||
return (Class<?>) ((ParameterizedType) type).getRawType();
|
|
||||||
|
|
||||||
// Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
|
|
||||||
// return (Class<?>) actualTypeArguments[0];
|
|
||||||
}
|
|
||||||
else if (type instanceof TypeVariable) {
|
|
||||||
// we have a COMPLEX type parameter
|
|
||||||
Type[] bounds = ((TypeVariable<?>) type).getBounds();
|
|
||||||
if (bounds.length > 0) {
|
|
||||||
return getRawTypeAsClass(bounds[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new RuntimeException("Unknown/messed up type parameter . Can't figure it out... Quit being complex!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if clazz or interface directly has one of the interfaces defined by clazzItMustHave
|
|
||||||
* <p/>
|
* <p/>
|
||||||
* If the class DOES NOT directly have the interface it will fail.
|
* If the class DOES NOT directly have the interface it will fail.
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
boolean hasInterface(Class<?> clazzItMustHave, Class<?> clazz) {
|
boolean hasInterface(Class<?> requiredClass, Class<?> clazz) {
|
||||||
if (clazzItMustHave == clazz) {
|
if (requiredClass == clazz) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?>[] interfaces = clazz.getInterfaces();
|
Class<?>[] interfaces = clazz.getInterfaces();
|
||||||
for (Class<?> iface : interfaces) {
|
for (Class<?> iface : interfaces) {
|
||||||
if (iface == clazzItMustHave) {
|
if (iface == requiredClass) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// now walk up to see if we can find it.
|
// now walk up to see if we can find it.
|
||||||
for (Class<?> iface : interfaces) {
|
for (Class<?> iface : interfaces) {
|
||||||
boolean b = hasInterface(clazzItMustHave, iface);
|
boolean b = hasInterface(requiredClass, iface);
|
||||||
if (b) {
|
if (b) {
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
@ -160,7 +111,7 @@ class ClassHelper {
|
|||||||
// don't check for Object.class (this is where superclass is null)
|
// don't check for Object.class (this is where superclass is null)
|
||||||
while (superClass != null && superClass != Object.class) {
|
while (superClass != null && superClass != Object.class) {
|
||||||
// check to see if we have what we are looking for on our CURRENT class
|
// check to see if we have what we are looking for on our CURRENT class
|
||||||
if (hasInterface(clazzItMustHave, superClass)) {
|
if (hasInterface(requiredClass, superClass)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
616
src/dorkbox/util/TypeResolver.java
Normal file
616
src/dorkbox/util/TypeResolver.java
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2017 Jonathan Halterman
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* From: https://github.com/jhalterman/typetools
|
||||||
|
*/
|
||||||
|
package dorkbox.util;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.lang.reflect.AccessibleObject;
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.GenericArrayType;
|
||||||
|
import java.lang.reflect.Member;
|
||||||
|
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.security.AccessController;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced type resolution utilities.
|
||||||
|
*
|
||||||
|
* @author Jonathan Halterman
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("restriction")
|
||||||
|
public final
|
||||||
|
class TypeResolver {
|
||||||
|
/**
|
||||||
|
* Cache of type variable/argument pairs
|
||||||
|
*/
|
||||||
|
private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> TYPE_VARIABLE_CACHE = Collections.synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>());
|
||||||
|
|
||||||
|
private static volatile boolean CACHE_ENABLED = true;
|
||||||
|
private static boolean RESOLVES_LAMBDAS;
|
||||||
|
|
||||||
|
private static Method GET_CONSTANT_POOL;
|
||||||
|
private static Method GET_CONSTANT_POOL_SIZE;
|
||||||
|
private static Method GET_CONSTANT_POOL_METHOD_AT;
|
||||||
|
|
||||||
|
private static final Map<String, Method> OBJECT_METHODS = new HashMap<String, Method>();
|
||||||
|
private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPERS;
|
||||||
|
|
||||||
|
private static final Double JAVA_VERSION;
|
||||||
|
|
||||||
|
static {
|
||||||
|
JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version", "0"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Unsafe unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<Unsafe>() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
Unsafe run() throws Exception {
|
||||||
|
final Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
||||||
|
f.setAccessible(true);
|
||||||
|
|
||||||
|
return (Unsafe) f.get(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool");
|
||||||
|
String constantPoolName = JAVA_VERSION < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool";
|
||||||
|
Class<?> constantPoolClass = Class.forName(constantPoolName);
|
||||||
|
GET_CONSTANT_POOL_SIZE = constantPoolClass.getDeclaredMethod("getSize");
|
||||||
|
GET_CONSTANT_POOL_METHOD_AT = constantPoolClass.getDeclaredMethod("getMethodAt", int.class);
|
||||||
|
|
||||||
|
// setting the methods as accessible
|
||||||
|
Field overrideField = AccessibleObject.class.getDeclaredField("override");
|
||||||
|
long overrideFieldOffset = unsafe.objectFieldOffset(overrideField);
|
||||||
|
unsafe.putBoolean(GET_CONSTANT_POOL, overrideFieldOffset, true);
|
||||||
|
unsafe.putBoolean(GET_CONSTANT_POOL_SIZE, overrideFieldOffset, true);
|
||||||
|
unsafe.putBoolean(GET_CONSTANT_POOL_METHOD_AT, overrideFieldOffset, true);
|
||||||
|
|
||||||
|
// additional checks - make sure we get a result when invoking the Class::getConstantPool and
|
||||||
|
// ConstantPool::getSize on a class
|
||||||
|
Object constantPool = GET_CONSTANT_POOL.invoke(Object.class);
|
||||||
|
GET_CONSTANT_POOL_SIZE.invoke(constantPool);
|
||||||
|
|
||||||
|
for (Method method : Object.class.getDeclaredMethods()) {
|
||||||
|
OBJECT_METHODS.put(method.getName(), method);
|
||||||
|
}
|
||||||
|
|
||||||
|
RESOLVES_LAMBDAS = true;
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Class<?>, Class<?>> types = new HashMap<Class<?>, Class<?>>();
|
||||||
|
types.put(boolean.class, Boolean.class);
|
||||||
|
types.put(byte.class, Byte.class);
|
||||||
|
types.put(char.class, Character.class);
|
||||||
|
types.put(double.class, Double.class);
|
||||||
|
types.put(float.class, Float.class);
|
||||||
|
types.put(int.class, Integer.class);
|
||||||
|
types.put(long.class, Long.class);
|
||||||
|
types.put(short.class, Short.class);
|
||||||
|
types.put(void.class, Void.class);
|
||||||
|
PRIMITIVE_WRAPPERS = Collections.unmodifiableMap(types);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An unknown type.
|
||||||
|
*/
|
||||||
|
public static final
|
||||||
|
class Unknown {
|
||||||
|
private
|
||||||
|
Unknown() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the internal caching of resolved TypeVariables. (Default is true)
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
void enableCache() {
|
||||||
|
CACHE_ENABLED = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the internal caching of resolved TypeVariables. (Default is true)
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
void disableCache() {
|
||||||
|
TYPE_VARIABLE_CACHE.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 (RESOLVES_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 genericType 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<?> component = resolveRawClass(arrayType.getGenericComponentType(), subType, functionalInterface);
|
||||||
|
return Array.newInstance(component, 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 = TYPE_VARIABLE_CACHE.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) {
|
||||||
|
TYPE_VARIABLE_CACHE.put(targetType, new WeakReference<Map<TypeVariable<?>, Type>>(map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (RESOLVES_LAMBDAS) {
|
||||||
|
// Find SAM
|
||||||
|
for (Method m : functionalInterface.getMethods()) {
|
||||||
|
if (!isDefaultMethod(m) && !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();
|
||||||
|
|
||||||
|
Member member = getMemberRef(lambdaType);
|
||||||
|
if (member == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate return type argument
|
||||||
|
if (returnTypeVar instanceof TypeVariable) {
|
||||||
|
Class<?> returnType = member instanceof Method
|
||||||
|
? ((Method) member).getReturnType()
|
||||||
|
: ((Constructor<?>) member).getDeclaringClass();
|
||||||
|
returnType = wrapPrimitives(returnType);
|
||||||
|
if (!returnType.equals(Void.class)) {
|
||||||
|
map.put((TypeVariable<?>) returnTypeVar, returnType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?>[] arguments = member instanceof Method
|
||||||
|
? ((Method) member).getParameterTypes()
|
||||||
|
: ((Constructor<?>) member).getParameterTypes();
|
||||||
|
|
||||||
|
// Populate object type from arbitrary object method reference
|
||||||
|
int paramOffset = 0;
|
||||||
|
if (paramTypeVars.length > 0 && paramTypeVars[0] instanceof TypeVariable &&
|
||||||
|
paramTypeVars.length == arguments.length + 1) {
|
||||||
|
Class<?> instanceType = member.getDeclaringClass();
|
||||||
|
map.put((TypeVariable<?>) paramTypeVars[0], instanceType);
|
||||||
|
paramOffset = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle additional arguments that are captured from the lambda's enclosing scope
|
||||||
|
int argOffset = 0;
|
||||||
|
if (paramTypeVars.length < arguments.length) {
|
||||||
|
argOffset = arguments.length - paramTypeVars.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate type arguments
|
||||||
|
for (int i = 0; i + argOffset < arguments.length; i++) {
|
||||||
|
if (paramTypeVars[i] instanceof TypeVariable) {
|
||||||
|
map.put((TypeVariable<?>) paramTypeVars[i + paramOffset], wrapPrimitives(arguments[i + argOffset]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
boolean isDefaultMethod(Method m) {
|
||||||
|
return JAVA_VERSION >= 1.8 && m.isDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
Member getMemberRef(Class<?> type) {
|
||||||
|
Object constantPool;
|
||||||
|
try {
|
||||||
|
constantPool = GET_CONSTANT_POOL.invoke(type);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Member result = null;
|
||||||
|
for (int i = getConstantPoolSize(constantPool) - 1; i >= 0; i--) {
|
||||||
|
Member member = getConstantPoolMethodAt(constantPool, i);
|
||||||
|
// Skip SerializedLambda constructors and members of the "type" class
|
||||||
|
if (member == null || (member instanceof Constructor && member.getDeclaringClass()
|
||||||
|
.getName()
|
||||||
|
.equals("java.lang.invoke.SerializedLambda")) ||
|
||||||
|
member.getDeclaringClass()
|
||||||
|
.isAssignableFrom(type)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = member;
|
||||||
|
|
||||||
|
// Return if not valueOf method
|
||||||
|
if (!(member instanceof Method) || !isAutoBoxingMethod((Method) member)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
boolean isAutoBoxingMethod(Method method) {
|
||||||
|
Class<?>[] parameters = method.getParameterTypes();
|
||||||
|
return method.getName()
|
||||||
|
.equals("valueOf") && parameters.length == 1 && parameters[0].isPrimitive() && wrapPrimitives(parameters[0]).equals(
|
||||||
|
method.getDeclaringClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
Class<?> wrapPrimitives(Class<?> clazz) {
|
||||||
|
return clazz.isPrimitive() ? PRIMITIVE_WRAPPERS.get(clazz) : clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
int getConstantPoolSize(Object constantPool) {
|
||||||
|
try {
|
||||||
|
return (Integer) GET_CONSTANT_POOL_SIZE.invoke(constantPool);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
Member getConstantPoolMethodAt(Object constantPool, int i) {
|
||||||
|
try {
|
||||||
|
return (Member) GET_CONSTANT_POOL_METHOD_AT.invoke(constantPool, i);
|
||||||
|
} catch (Exception ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private
|
||||||
|
TypeResolver() {
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user