WILLIAM WOODMAN
WILLIAM WOODMAN

Reputation: 1231

How should one use LambdaMetafactory to generate an invoke dynamic callsite

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

Answers (1)

WILLIAM WOODMAN
WILLIAM WOODMAN

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

Related Questions