Search code examples
javamethodhandle

MethodHandle with general non-void return filter?


I'm trying to establish a MethodHandle that has a general purpose filter for the return values, by using the MethodHandles.filterReturnValue() to do the work.

The problem I have is that I don't know (nor care about) the return type, so I was hoping to just wire up a Object myfilter(Object obj) as the MethodHandle to filter the return objects through. However, this isn't apparently allowed in the MethodHandles.filterReturnValue() call.

Here's what I was hoping would work (but doesn't)...

package invoke;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Arrays;

public class MethodHandleReturnExample
{
    public static void main(String[] args) throws Throwable
    {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        Testcase test = new Testcase();
        MethodHandle onCount = findMethodByName(lookup, test, "onCount");
        MethodHandle onNada = findMethodByName(lookup, test, "onNada");
        MethodHandle onText = findMethodByName(lookup, test, "onText");

        onNada.invoke("hello");
        onText.invoke("world");
        onCount.invoke();
        onCount.invoke();
        onCount.invoke();
    }

    private static MethodHandle findMethodByName(MethodHandles.Lookup lookup, Object obj, String methodName) throws IllegalAccessException, NoSuchMethodException
    {
        Method method = Arrays.stream(obj.getClass().getDeclaredMethods())
                .filter(m -> m.getName().equalsIgnoreCase(methodName))
                .findFirst().get();
        MethodHandle handle = lookup.unreflect(method);
        handle = handle.bindTo(obj);

        if (handle.type().returnType() != Void.TYPE)
        {
            MethodHandle returnFilter = lookup.findVirtual(Util.class, "filter", MethodType.methodType(Object.class,Object.class));
            returnFilter = returnFilter.bindTo(new Util());
            handle = MethodHandles.filterReturnValue(handle, returnFilter);
        }
        return handle;
    }

    public static class Testcase
    {
        int count = 0;

        public int onCount()
        {
            int ret = ++count;
            System.out.printf("onCount() : %d%n", ret);
            return ret;
        }

        public void onNada(String msg)
        {
            System.out.printf("onNada(%s)%n", msg);
        }

        public String onText(String msg)
        {
            System.out.printf("onText(%s)%n", msg);
            return "[text:" + msg + "]";
        }
    }

    public static class Util
    {
        public Object filter(Object obj)
        {
            System.out.printf("# filter((%s) %s)%n", obj.getClass().getName(), obj);
            return obj;
        }
    }
}

It seems that MethodHandles.filterReturnValue() is inappropriate for this purpose.

I was then hoping that I could perhaps make a MethodHandle that called another MethodHandle, but that started to get complicated.

eg:

public Object filter(MethodHandle handle, Object ... args)
{
    Object ret = handle.invoke(args);
    System.out.printf("# filter((%s) %s)%n", ret.getClass().getName(), ret);
    return ret;
}

I've tried to get my head around MethodHandleProxies or even LamdaMetafactory, but they are tough to understand from the javadoc and meager examples found online.


Solution

  • filterReturnValue will call the target handle is if it were using invokeExact, so the return type has to match exactly.

    So you have to adapt the return types to be Object. The easiest way is to use asType (this will automatically box the int too):

    if (handle.type().returnType() != Void.TYPE) {
        handle = handle.asType(handle.type().changeReturnType(Object.class)); // <---
    
        MethodHandle returnFilter = lookup.findVirtual(Util.class, "filter",
                MethodType.methodType(Object.class, Object.class));
        returnFilter = returnFilter.bindTo(new Util());
        handle = MethodHandles.filterReturnValue(handle, returnFilter);
    }