Last active
August 29, 2018 13:51
-
-
Save jeppec/86bae26805f6088c7361 to your computer and use it in GitHub Desktop.
Reified generics for Java 8 Lambdas
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sun.reflect.ConstantPool; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.ParameterizedType; | |
import java.lang.reflect.Type; | |
import java.util.Objects; | |
/** | |
* This approach was inspired by code from @danielbodart | |
*/ | |
public class Reflect { | |
private static final Method getConstantPoolMethod; | |
static { | |
try { | |
getConstantPoolMethod = Class.class.getDeclaredMethod("getConstantPool"); | |
getConstantPoolMethod.setAccessible(true); | |
} catch (NoSuchMethodException e) { | |
throw new RuntimeException("Couldn't find the 'getConstantPool' method - are you using the SUN JVM?", | |
e); | |
} | |
} | |
/** | |
* Resolve the generic argument types of a lamb instance (even though types are not reified in Java)<br/> | |
* Example: If a class/method is handed a Function<Foo, Bar> then resolveGenericLambdaArgumentType for argument 0 (the only argument) will return String.class: | |
* <pre> | |
* Function<String, Boolean> myStrToBoolFunc = strArg -> true; | |
* assertThat(Reflect.resolveGenericLambdaArgumentType(myStrToBoolFunc, 0), equalTo(String.class)); | |
* </pre> | |
* Example of a lambda with multiple arguments: | |
* <pre> | |
* BiConsumer<Integer, Long> biConsumer = (i, l) -> {}; | |
* assertThat(Reflect.resolveGenericLambdaArgumentType(biConsumer, 0), equalTo(Integer.class)); | |
* assertThat(Reflect.resolveGenericLambdaArgumentType(biConsumer, 1), equalTo(Long.class)); | |
* </pre> | |
*/ | |
public static Class<?> resolveLambdaArgumentType(Object object, int argumentIndex) { | |
Objects.requireNonNull(object, | |
"object"); | |
if (argumentIndex < 0) { | |
throw new IllegalArgumentException("ArgumentIndex must be >= 0"); | |
} | |
Type genericInterface = object.getClass() | |
.getGenericInterfaces()[0]; | |
if (genericInterface instanceof ParameterizedType) { | |
return (Class<?>) ((ParameterizedType) genericInterface).getActualTypeArguments()[argumentIndex]; | |
} | |
else { | |
String[] methodRef = resolveMethodRef(object, "Failed to resolve generic type arguments for " + object + " of type " + genericInterface.toString()); | |
try { | |
String argumentType = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodRef[2])[argumentIndex].getClassName(); | |
return Class.forName(argumentType); | |
} catch (Exception e) { | |
throw new RuntimeException("Failed to resolve generic type arguments for " + object + " of type " + genericInterface | |
.toString(), | |
e); | |
} | |
} | |
} | |
/** | |
* Resolve the generic return type of an lambda instance (even though types are not reified in Java)<br/> | |
* Example: If a class/method is handed a Function<Foo, Bar> then resolveGenericLambdaReturnType will return Boolean.class: | |
* <pre> | |
* Function<String, Boolean> myStrToBoolFunc = strArg -> true; | |
* assertThat(Reflect.resolveGenericLambdaReturnType(myStrToBoolFunc), equalTo(Boolean.class)); | |
* </pre> | |
*/ | |
public static Class<?> resolveLambdaReturnType(Object object) { | |
Objects.requireNonNull(object, | |
"object"); | |
String[] methodRef = resolveMethodRef(object, "Failed to resolve generic return type for " + object + " of type " + object.getClass() | |
.toString()); | |
try { | |
String returnType = jdk.internal.org.objectweb.asm.Type.getReturnType(methodRef[2]) | |
.getClassName(); | |
return Class.forName(returnType); | |
} catch (Exception e) { | |
throw new RuntimeException("Failed to resolve generic return type for " + object + " of type " + object.getClass() | |
.toString(), | |
e); | |
} | |
} | |
private static String[] resolveMethodRef(Object object, String message) { | |
ConstantPool constantPool = getConstantPool(object, message); | |
int at = 15; | |
String[] methodRef = null; | |
// Try and resolve the methodRef, but stop if advancing the at doesn't turn out a result | |
while (methodRef == null && at < 100) { | |
try { | |
// This is pretty slow and somewhat idiotic, but works and gives us reified generics in Java 8 | |
methodRef = constantPool.getMemberRefInfoAt(at); | |
} catch (IllegalArgumentException e) { | |
// For every generic argument the index forward | |
at += 1; | |
} | |
} | |
if (methodRef == null) { | |
throw new RuntimeException(message); | |
} | |
return methodRef; | |
} | |
private static ConstantPool getConstantPool(Object object, String message) { | |
try { | |
return (ConstantPool) getConstantPoolMethod.invoke(object.getClass()); | |
} catch (IllegalAccessException | InvocationTargetException e) { | |
throw new RuntimeException(message, e); | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment