'How to change MethodHandle arguments after being inserted?

Suppose you have MethodHandle and some arguments have been specified, how to change those arguments after being set?

import static java.lang.invoke.MethodType.*;
import static java.lang.invoke.MethodHandles.*;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;

public class SomeTest {

    public static void main(String[] args) throws Throwable {

        MethodHandle methodHandle = MethodHandles.lookup().findVirtual(SomeTest.class,
                "someMethod", methodType(void.class, String.class));

        methodHandle = MethodHandles.insertArguments(methodHandle, 1, "Hi");

        // this invoke calls with "Hi", which is fine
        methodHandle.invoke(new SomeTest());

        // here, how to change the arguments to be e.g. "Hello" instead of "Hi"

        methodHandle.invoke(new SomeTest());

    }

    public void someMethod(String a) {
        System.out.println("Called with " + a);
    }
}

I have tried to use MethodHandles.filterArguments()

....
    methodHandle = MethodHandles.filterArguments(methodHandle, 1,
            MethodHandles.lookup().findStatic(SomeTest.class, "returnSomething",
                    methodType(String.class)));

    methodHandle.invoke(new SomeTest());
}

public static String returnSomething() {
    return "Hello";
}

but I get an exception:

Exception in thread "main" java.lang.IllegalArgumentException: too many filters
    at java.lang.invoke.MethodHandleStatics.newIllegalArgumentException(MethodHandleStatics.java:139)
    at java.lang.invoke.MethodHandles.filterArgumentsCheckArity(MethodHandles.java:2623)
    at java.lang.invoke.MethodHandles.filterArguments(MethodHandles.java:2595)
    at test.test.SomeTest.main(SomeTest.java:22)


Solution 1:[1]

2 methods:

  • Reuse your original method handle and bind it to another string:

    MethodHandle methodHandle = MethodHandles.lookup().findVirtual(
             SomeTest.class,
             "someMethod",
             methodType(void.class, String.class)
    );
    MethodHandle hi    = MethodHandles.insertArguments(methodHandle, 1, "Hi");
    MethodHandle hello = MethodHandles.insertArguments(methodHandle, 1, "Hello");
    hi.invoke(new SomeTest()); // "Hi"
    hello.invoke(new SomeTest()); // "Hello"
    
  • Bind the second argument to a getter of a class member, which you manipulate. You have to filter the arguments with an "exactInvoker" that will perform the getter to actually get the String value. See:

    public class SomeTest {
      public static class StringHolder {
        public String toPrint;
    
        StringHolder(String toPrint) {
          this.toPrint = toPrint;
        }
      }
    
      public static void main(String[] args) throws Throwable {
        MethodHandle toPrintGetter = MethodHandles.lookup().findGetter(
                StringHolder.class,
                "toPrint", 
                String.class);
        MethodHandle someMethod = MethodHandles.lookup().findVirtual(
                SomeTest.class,
                "someMethod", 
                MethodType.methodType(void.class, String.class)
        );
    
        StringHolder holder = new StringHolder("Hi");
        someMethod = MethodHandles.filterArguments(
                someMethod,
                1,
                MethodHandles.exactInvoker(MethodType.methodType(String.class))
        );
        MethodHandle stringPrinter = MethodHandles.insertArguments(
                someMethod,
                1, 
                toPrintGetter.bindTo(holder)
        );
    
        stringPrinter.invokeExact(new SomeTest()); // prints "Hi"
        holder.toPrint = "Hello";
        stringPrinter.invokeExact(new SomeTest()); // prints "Hello"
      }
    
      public void someMethod(String a) {
        System.out.println("Called with " + a);
      }
    }
    

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Brice