Reputation: 1231
I am trying to work out how to use LambdaMetafactory to generate a usable callSite.
Here's my latest Groovy script attempt. I have tried multiple permutations of parameters and cannot get the second getter based example to work.
The first example I did eventually get to work generating a Supplier from a closure after much fiddling and reading around the Java documentation and Stack Overflow.
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.util.function.Supplier
/**
* LambdaMetafactory example with closure - works
*/
Closure myClosure = {"hello from closure"}
MethodHandles.Lookup lookup= MethodHandles.lookup()
def delegateImpl = lookup.findVirtual(Closure.class, "call", MethodType.methodType (Object.class))
//now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
java.lang.invoke.CallSite closureCallSite = LambdaMetafactory.metafactory(
lookup,
"get",
MethodType.methodType(Supplier.class, Closure.class),
MethodType.methodType (Object.class),
delegateImpl,
MethodType.methodType(String)
)
MethodHandle closureFactory = closureCallSite.getTarget()
Supplier closureLambda = closureFactory.bindTo(myClosure).invokeWithArguments()
def res = closureLambda.get()
println res
/**
* LambdaMetafactory example with standard class - cant get to work with any combinations
*/
class ExampleClass {
private String value = "hello from getter"
ExampleClass() {} //constructor
String getValue () {return value}
void setValue (String val) {value = val}
}
ExampleClass instance = new ExampleClass()
MethodHandle getterDelegateImpl = lookup.findVirtual(ExampleClass.class, "getValue", MethodType.methodType (String.class))
java.lang.invoke.CallSite getterCallSite = LambdaMetafactory.metafactory(
lookup,
"get",
MethodType.methodType(Supplier.class, ExampleClass),
MethodType.methodType (Object.class),
getterDelegateImpl,
MethodType.methodType(String.class)
)
MethodHandle classFactory = getterCallSite.getTarget()
Supplier lambda = classFactory.bindTo(instance).invokeWithArguments()
def ret = lambda.get ()
println ret
When you run this, the first example works and I can get a valid callSite to get a dynamic Supplier reference from Closure. The second example takes the same approach but using a standard class with a getter method.
hello from closure
Caught: java.lang.invoke.LambdaConversionException: Exception finding constructor
java.lang.invoke.LambdaConversionException: Exception finding constructor
at script.testLambdaMetafactory.run(testLambdaMetafactory.groovy:69)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
Caused by: java.lang.IllegalAccessException: no such constructor: org.codehaus.groovy.vmplugin.v8.IndyInterface$$InjectedInvoker_0x0000000800dc2000$$Lambda$261/0x0000000800dc6a78.<init>(ExampleClass)void/invokeSpecial
... 4 more
Caused by: java.lang.LinkageError: bad method type alias: (ExampleClass)void not visible from class org.codehaus.groovy.vmplugin.v8.IndyInterface$$InjectedInvoker_0x0000000800dc2000$$Lambda$261/0x0000000800dc6a78
... 4 more
I have tried many permutations of types, numbers of params for methodTypes to no effect.
What do I have to do to generate workable code given that I want to generate invokeDynamic references that work?
Upvotes: 1
Views: 714
Reputation: 1231
Ok - seems that part of this was the fact i using a groovy script and decalred my bean class in that script. LamdbaMetafactory didn't like. So i separated the bean class into its own class file
package lamda
class ExampleBeanClass {
private String value = "hello from getter"
private static String staticValue = "static string value"
ExampleBeanClass() {} //constructor
String getValue () {return value}
void setValue (String val) {value = val}
static String getStaticValue () {return staticValue}
static String setStaticValue (String staticVal) {staticValue = staticVal}
}
now you can write some tests - i've shown three here, one using access via a Supplier and the other by a Function generated interface. If your accessing non static methods, you need to bind the instance, and it needs to be declared in the invokedType param.
If your invoking a static method - you dont need to declare the bean in the invokedType, and you dont need to bind an instance
These three tests are now working
package lambda
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import lamda.ExampleBeanClass
import java.lang.invoke.CallSite
import java.lang.invoke.LambdaMetafactory
import java.lang.invoke.MethodHandle
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Method
import java.util.function.Function
import java.util.function.Supplier
class InvokeDynamicTest {
ExampleBeanClass bean
MethodHandles.Lookup callerCtx
MethodHandle implementationDelegate
@BeforeEach
void init() {
bean = new ExampleBeanClass()
callerCtx = MethodHandles.lookup()
}
@Test
void dummyTest () {
assert true
assert bean
assert callerCtx
}
/**
* get value by generating a Supplier interface reference
*/
@Test
void accessViaNonStaticBeanSupplierGetter () {
Method reflected = ExampleBeanClass.class.getDeclaredMethod("getValue")
implementationDelegate = callerCtx.unreflect (reflected)
//if you want bind an instance value to your lambda, you have to include those types in the InvokedType signature, and then do the bind
CallSite site = LambdaMetafactory.metafactory(
callerCtx,
"get", //functional interface method name
MethodType.methodType (Supplier.class, ExampleBeanClass), //invoked type
MethodType.methodType (Object.class), // SAM method type signature of required interface
implementationDelegate, //code thats doing the real work
MethodType.methodType (String) //expected return type of instantiated method, expected as subtype of SAM type
)
MethodHandle factory = site.getTarget().bindTo (bean) //invokedType defined bean class, so now bind one here
Supplier func = (Supplier) factory.invokeWithArguments()
assert func.get() == "hello from getter"
}
/**
* get value by generating a functional interface reference
*/
@Test
void accessViaNonStaticBeanFunctionTypeGetter () {
Method reflected = ExampleBeanClass.class.getDeclaredMethod("getValue")
implementationDelegate = callerCtx.unreflect (reflected)
//if you want bind an instance value to your lambda, you have to include those types in the InvokedType signature, and then do the bind
CallSite site = LambdaMetafactory.metafactory(
callerCtx,
"apply", //functional interface method name
MethodType.methodType (Function.class, ExampleBeanClass), //invoked type
MethodType.methodType (Object.class), // SAM method type signature of required interface
implementationDelegate, //code thats doing the real work
MethodType.methodType (String) //expected return type of instantiated method, expected as subtype of SAM type
)
MethodHandle factory = site.getTarget().bindTo (bean) //invokedType defined bean class, so now bind one here
Function func = (Function) factory.invokeWithArguments()
assert func.apply() == "hello from getter"
}
@Test
void accessViaStaticBeanGetter () {
Method reflected = ExampleBeanClass.class.getDeclaredMethod("getStaticValue")
implementationDelegate = callerCtx.unreflect (reflected)
//as we are invoking static type we don't need to bind an instance to the site for this test case
CallSite site = LambdaMetafactory.metafactory(
callerCtx,
"get", //functional interface method name
MethodType.methodType (Supplier.class), //invoked type, doesnt need bean class for static invocation
MethodType.methodType (Object.class), // SAM method type signature of required interface
implementationDelegate, //code thats doing the real work
MethodType.methodType (String) //expected return type of instantiated method, expected as subtype of SAM type
)
MethodHandle factory = site.getTarget()
Supplier func = (Supplier) factory.invokeWithArguments()
assert func.get() == "static string value"
}
}
I hope this may safe several hours thrashing on how to use the LambdaMetafactory. It is however remains very pernickety to use, and understand how to drive it, should you want to use it
To help improve the fiddley use i've tried to generate a couple of static 'helper' methods that disguises some of the fiddly type matching for you.
class ClassUtils {
private static MethodHandles.Lookup lookup = MethodHandles.lookup()
/**
* generates a functional interface from a callSite
*
* @param functionalInterfaceReturnClass
* @param instance
* @param sourceMethodName
* @param sourceMethodArgTypes
* @return
*/
static def getLambdaFromReflectionMethod(Class<?> functionalInterfaceReturnClass, Object instance, String sourceMethodName, Class<?>... sourceMethodArgTypes) {
Method reflectedCall
String funcionalInterfaceMethodName
switch (functionalInterfaceReturnClass) {
case Supplier -> funcionalInterfaceMethodName = "get"
case Function -> funcionalInterfaceMethodName = "apply"
case BiFunction -> funcionalInterfaceMethodName = "apply"
case Consumer -> funcionalInterfaceMethodName = "accept"
case Predicate -> funcionalInterfaceMethodName = "test"
case Callable -> funcionalInterfaceMethodName = "call"
case Runnable -> funcionalInterfaceMethodName = "run"
default -> funcionalInterfaceMethodName = "apply"
}
Class runtimeClazz = instance.getClass()
def size = sourceMethodArgTypes.size()
if (sourceMethodArgTypes?.size() > 0 ) {
reflectedCall = instance.class.getMethod(sourceMethodName, *sourceMethodArgTypes )
} else {
reflectedCall = instance.class.getMethod(sourceMethodName)
}
MethodHandle delegateImplHandle = lookup.unreflect(reflectedCall)
MethodType invokedMethodType = MethodType.methodType(functionalInterfaceReturnClass, runtimeClazz)
MethodType samMethodType = (instance instanceof Closure ) ? MethodType.methodType (Object)
: delegateImplHandle.type().dropParameterTypes(0,1).erase()
MethodType instantiatedMethodType = (instance instanceof Closure ) ? MethodType.methodType (Object)
: delegateImplHandle.type().dropParameterTypes(0,1)
//now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
lookup, //calling Ctx for methods
funcionalInterfaceMethodName, //name of the functional interface name to invoke
invokedMethodType, // MethodType.methodType(Supplier, Closure ),
samMethodType, //MethodType.methodType(Object), // samMthodType: signature and return type of method to be implemented after type erasure
delegateImplHandle, //implMethod handle that does the work - the handle for closure call()
instantiatedMethodType //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
)
MethodHandle factory = callSite.getTarget()
return factory.bindTo(instance).invokeWithArguments()
}
/**
* generates a functional interface from a callSite
*
* @param returnClass
* @param instance
* @param sourceMethodName
* @param args
* @return
*/
static def getLambdaFromStaticReflectionMethod(Class<?> functionalInterfaceClass, Class<?> sourceClazz, String sourceMethodName, Class<?>... sourceMethodArgTypes) {
Method reflectedCall
String functionalInterfaceMethodName
switch (functionalInterfaceClass) {
case Supplier -> functionalInterfaceMethodName = "get"
case Function -> functionalInterfaceMethodName = "apply"
case BiFunction -> functionalInterfaceMethodName = "apply"
case Consumer -> functionalInterfaceMethodName = "accept"
case Predicate -> functionalInterfaceMethodName = "test"
case Callable -> functionalInterfaceMethodName = "call"
case Runnable -> functionalInterfaceMethodName = "run"
default -> functionalInterfaceMethodName = "apply"
}
Class runtimeClazz = sourceClazz
Class closClazz = Closure.class
if (sourceMethodArgTypes?.size() > 0 )
reflectedCall = runtimeClazz.getMethod(sourceMethodName, *sourceMethodArgTypes )
else
reflectedCall = runtimeClazz.getMethod(sourceMethodName )
MethodHandle delegateImplHandle = lookup.unreflect(reflectedCall)
/**
* weird with closure instantiatedMethodType, and samMethodType seem to need form ()<returnType>
* if using instance of ordinary class you can get form (<source>)<returnType>
*/
MethodType invokedMethodType = MethodType.methodType(functionalInterfaceClass)
MethodType samMethodType = delegateImplHandle.type().erase()
MethodType instantiatedMethodType = delegateImplHandle.type()
/**
* wont work at mo for static functions to be generated
*/
//now get a callSite for the handle - https://wttech.blog/blog/2020/method-handles-and-lambda-metafactory/
java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
lookup, //calling Ctx for methods
functionalInterfaceMethodName, //name of the functional interface name to invoke
invokedMethodType, // MethodType.methodType(Supplier, Closure ),
samMethodType, //MethodType.methodType(Object), // samMthodType: signature and return type of method to be implemented after type erasure
delegateImplHandle, //implMethod handle that does the work - the handle for closure call()
instantiatedMethodType //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
)
MethodHandle factory = callSite.getTarget()
return ( factory.invokeWithArguments() ).asType(functionalInterfaceClass)
}
}
and some tests to show this working along side the raw access i was using to cross check my MethodTypes used for the direct use of the LambdaFactory. This has not tested all the options rigorously, but the sample cases tested should work.
class ClassUtilsTest {
@Test
void generateSupplierFromClosure () {
Closure myClos = {"hello"}
Supplier supplier = ClassUtils.getLambdaFromReflectionMethod(Supplier, myClos, 'call')
supplier.get() == "hello"
}
@Test
void generateSupplierFromBeanClassInstance () {
ExampleBeanClass bean = new ExampleBeanClass()
Supplier supplier = ClassUtils.getLambdaFromReflectionMethod(Supplier, bean, 'getValue')
supplier.get() == "hello from getter"
}
/**
* slightly unnatural but you can get a functional interface for a getter,
* when you invoke just invoke with empty args list -
* bit using Supplier interface feels better fit
*/
@Test
void generateFunctionFromBeanClassInstance () {
ExampleBeanClass bean = new ExampleBeanClass()
Function function = ClassUtils.getLambdaFromReflectionMethod(Function, bean, 'getValue')
function.apply() == "hello from getter"
}
@Test
void generateSupplierFromBeanClassStaticMethod () {
ExampleBeanClass bean = new ExampleBeanClass()
Supplier supplier = ClassUtils.getLambdaFromStaticReflectionMethod(Supplier, bean.getClass(), 'getStaticValue')
supplier.get() == "static string value"
}
@Test
void generatePredicateFromBeanClassInstance () {
ExampleBeanClass bean = new ExampleBeanClass()
Predicate predicate = ClassUtils.getLambdaFromReflectionMethod(Predicate, bean, 'test', Object)
predicate.test(10) == true
}
@Test
void generateSupplierFromBeanClassInstanceViaCallSiteDirect () {
ExampleBeanClass bean = new ExampleBeanClass()
MethodHandles.Lookup lookup = MethodHandles.lookup()
MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'getValue',MethodType.methodType(String))
MethodType invokedMethodType = MethodType.methodType(Supplier, ExampleBeanClass)
MethodType sam = MethodType.methodType (Object.class)
MethodType samMethodTypeNoDrop = delegateImplHandle.type().erase()
MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
MethodType ins = MethodType.methodType (String.class)
MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)
java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
lookup, //calling Ctx for methods
'get', //name of the functional interface name to invoke
invokedMethodType, // MethodType.methodType(Supplier, Closure ),
samMethodType, //MethodType.methodType(Object), // samMthodType: signature and return type of method to be implemented after type erasure
delegateImplHandle, //implMethod handle that does the work - the handle for closure call()
instantiatedMethodType //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
)
MethodHandle factory = callSite.getTarget()
Supplier supplier = factory.bindTo(bean).invokeWithArguments()
supplier.get() == "hello from getter"
}
@Test
void generateFunctionFromBeanClassInstanceViaCallSiteDirect () {
ExampleBeanClass bean = new ExampleBeanClass()
MethodHandles.Lookup lookup = MethodHandles.lookup()
MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'getValue',MethodType.methodType(String))
MethodType invokedMethodType = MethodType.methodType(Function, ExampleBeanClass)
MethodType sam = MethodType.methodType (Object.class)
MethodType samMethodTypeNoDrop = delegateImplHandle.type().erase()
MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
MethodType ins = MethodType.methodType (String.class)
MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)
java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
lookup, //calling Ctx for methods
'apply', //name of the functional interface name to invoke
invokedMethodType, // MethodType.methodType(Supplier, Closure ),
samMethodType, //MethodType.methodType(Object), // samMthodType: signature and return type of method to be implemented after type erasure
delegateImplHandle, //implMethod handle that does the work - the handle for closure call()
instantiatedMethodType //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
)
MethodHandle factory = callSite.getTarget()
Function function = factory.bindTo(bean).invokeWithArguments()
function.apply() == "hello from getter"
}
@Test
void generatePredicateFromBeanClassInstanceViaCallSiteDirect () {
ExampleBeanClass bean = new ExampleBeanClass()
MethodHandles.Lookup lookup = MethodHandles.lookup()
MethodHandle delegateImplHandle = lookup.findVirtual(ExampleBeanClass,'test',MethodType.methodType(boolean.class, Object))
MethodType invokedMethodType = MethodType.methodType(Predicate, ExampleBeanClass)
MethodType sam = MethodType.methodType (boolean.class, Object)
MethodType samMethodType = delegateImplHandle.type().dropParameterTypes(0,1).erase()
MethodType ins = MethodType.methodType (boolean.class, Object)
MethodType instantiatedMethodType = delegateImplHandle.type().dropParameterTypes(0,1)
java.lang.invoke.CallSite callSite = LambdaMetafactory.metafactory(
lookup, //calling Ctx for methods
'test', //name of the functional interface name to invoke
invokedMethodType, // MethodType.methodType(Predicate, ExampleBeanClass ),
samMethodType, //MethodType.methodType(Object), // samMthodType: signature and return type of method to be implemented after type erasure
delegateImplHandle, //implMethod handle that does the work - the handle for closure call()
instantiatedMethodType //instantiatedMethodType: signature and return type that should be forced dynamically at invocation.
)
MethodHandle factory = callSite.getTarget()
Predicate predicate = factory.bindTo(bean).invokeWithArguments()
predicate.test(10) == true
}
}
Upvotes: 1