3

I have a method that is suppose to take in the classname, argument, and method name as parameters and invoke the method depending on which class it finds it in. The method:

Method[] methods = className.getDeclaredMethods();
 for (Method meth: methods) {
  if (meth.getName().equalsIgnoreCase(methodName)) {
   try {
    MethodType mt = MethodType.methodType(boolean.class, String.class);
    MethodHandles.Lookup caller = MethodHandles.lookup();
    MethodHandle handle = caller.findVirtual(Utilities.class, meth.getName(), mt);
    /*
     * Trying to invoke using lambdaMetaFactory
     */
    MethodType methodType = MethodType.methodType(boolean.class);
    MethodType invokedType = MethodType.methodType(BooleanSupplier.class);
    /*
     * Throws java.lang.invoke.LambdaConversionException: Incorrect number of parameters for instance method 
     * invokeVirtual com.grainger.Automation.Utilities.navigateToUrl:(String)boolean; 0 captured parameters, 
     * 0 functional interface method parameters, 1 implementation parameters
     */
    CallSite site = LambdaMetafactory.metafactory(caller, "getAsBoolean", invokedType, methodType, handle, methodType);

    MethodHandle factory = site.getTarget();
    BooleanSupplier r = (BooleanSupplier) factory.invoke();
    System.out.println(r.getAsBoolean());
    /*
     * Trying to invoke using Method Handle
     */
    /*
     * Trying to invoke the method handle here, but it fails with:
     * java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(Utilities,String)boolean to (Object)Object
     * methodArguments is an ArrayList<> that is initialized and populated before 
     */
    System.out.println(handle.invokeWithArguments(methodArguments.toArray())); //Output B
    /*
     * Invoking it directly with a string as an argument throws this execption:
     * com.sun.jdi.InvalidTypeException: Generated value (java.lang.String) is not compatible with declared type (java.lang.Object[]). occurred invoking method.
     */
    System.out.println(handle.invokeExact("www.google.com"));
    //                  keywordResult = (Boolean) handle.invoke("www.google.com");
    /*
     * Using the regular Reflections API, the method invoke works but is slow
     */
    //                  keywordResult = (Boolean) meth.invoke(classInstance, methodArguments); //Regular refection call to invoke the function
    break;
   } catch (Throwable e) {
    System.out.println(e.getMessage());
    keywordResult = false;
   }
  }
 }

The method I'm trying to invoke is located in a separate class within the same package. This is method definition:

public boolean navigateToUrl(String strUrl) {
return true;
}

When I try to invoke the function using the lambdametafactory, I get a LambdaConversionException. When I try to invoke the method using method handle, I get the exception Java.lang.invoke.WrongMethodTypeException when invoking with arguments and sending in an ArrayList<>. I get a com.sun.jdi.InvalidTypeException when doing invokExact with a String as the argument. All this is also listed above in the comments.

I'm not able to figure out what I'm doing wrong here. Any help would be greatly appreciated! If their is anything I'm missing from the code, please let me know!

Sahil Gupta
  • 285
  • 2
  • 5
  • 16

1 Answers1

7

Invoking a method through a MethodHandle usually goes in 4 steps:

  • Create a lookup object MethodHandles.Lookup that will be responsible for creating MethodHandle;
  • Instantiante a MethodType that represents the arguments class and return type class accepted and returned by a method handle;
  • Find a MethodHandle with the lookup and MethodType on a given class;
  • Invoke the MethodHandle with some arguments.

For example, let's say you want to invoke the method

public boolean navigateToUrl(String strUrl) {
    return true;
}

that is located in class FooBar.

public void invokeMethod() throws Throwable {
    // 1. Retrieves a Lookup
    MethodHandles.Lookup lookup = MethodHandles.lookup();

    // 2. Creates a MethodType
    MethodType methodType = MethodType.methodType(boolean.class, String.class);
    //                                            ^-----------^  ^----------^
    //                                             return type   argument class

    // 3. Find the MethodHandle
    MethodHandle handle = lookup.findVirtual(FooBar.class, "navigateToUrl", methodType);
    //                                       ^----------^  ^-------------^
    //                                            |        name of method
    //                             class from which method is accessed

    // 4. Invoke the method
    boolean b = (boolean) handle.invokeExact(fooBar, "test");
    //                                       ^----^  ^----^
    //                                          |    argument
    //                       instance of FooBar to invoke the method on

    System.out.println(b);
}

This is a sample code:

  • You can retrieve a different look-up, the one given above will have access to every method including private ones. You could restrict yourself to publicly accessible methods with publicLookup().
  • With a look-up, you can find methods but also fields or constructors.

You can refer to this question (and this one) for more information about what MethodHandles are.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • This works! I believe the only thing I was missing was passing in the instance of the class as an argument to both invokeExact and invokeWithArguments. Is it possible for me to accomplish this using the LambdaMetaFactory as well? Performance and code run time is something I need to consider and when doing my research I found that the Lambdametafactory can be a whole lot faster than either reflect or method handle. – Sahil Gupta Mar 10 '16 at 14:30
  • @SahilGupta Methodhandles will be a lot more performant than reflection yes! And `Lambdametafactory` relies on `MethodHandle` so it's the same. – Tunaki Mar 10 '16 at 14:32
  • So their is not much of a difference whether I use `Lambdametafactory` vs `MethodHandle`? I tried using the `lambdametafactory` above as well, by sending in the `booleanSupplier` functional interface and using the `Methodhandle` to look up the function. Do you think I should stick to what I have or try to use the `Lambdametafactory`? Even if using the `Lambdametafactory` is a fraction of a second faster, I would be more inclined to use that. – Sahil Gupta Mar 10 '16 at 14:47
  • @SahilGupta A `Lambdametafactory` is intended to be used by code generated by the compiler. It is preferable to use directly a `MethodHandle` as it performs more sanity checks. In terms of performance, I don't think there would be a real difference. But then I think a real performance measurement would be needed to make sure! – Tunaki Mar 10 '16 at 14:50
  • Thank you so much for all your help! I had been struggling with this for days, not knowing that I was missing a small key part of it. – Sahil Gupta Mar 10 '16 at 15:08