In the process of adapting pure reflective code (a Rexx-Java bridge) to use 
MethodHandles on Java 9
instead, everything seems to be working out so far.

In principle all invocations on the Java side are carried out by first using 
java.lang.reflect
(Field, Method, Constructor) using the supplied arguments (if the arguments can 
be coerced to the
respective parameterTypes it gets selected for invocation)  and if a candidate 
is found an
appropriate MethodHandle gets created, which then gets used to invoke the 
operation supplying the
coerced arguments, if any. Over the weekend I finalized the basic changes and 
started to test
against a set of sample/demo applications.

---

While testing a rather complex one (an adaption of the JavaFX address book 
example enhanced with a
BarChart, [1]), that exhibits a very strange behavior: when setting the values 
for the CategoryAxis
supplying an ObservableList of the month names in the current Locale, using a 
MethodHandle and
invoking it with invokeWithArguments() would yield (debug output):

    // // // RexxReflectJava9.processMethod(), ARRIVED: -> [INVOKE], 
tmpMethod=[public final void
    
javafx.scene.chart.CategoryAxis.setCategories(javafx.collections.ObservableList)]:
    method=[SETCATEGORIES] in
    
object=[rru.rexxArgs[1]="javafx.scene.chart.CategoryAxis@83278e1"/rru.bean="CategoryAxis[id=xAxis,
    styleClass=axis]"]

    // // // RexxReflectJava9.processMethod(),
    coercedArgs=[[[Ljava.lang.String;@57cfe770]].getClass().toString()=class 
[Ljava.lang.Object;,
             parameterTypes=[interface
    javafx.collections.ObservableList].getClass().toString()=class 
[Ljava.lang.Class;:,
             
rru.funcArgs=[[[Ljava.lang.String;@57cfe770]].getClass().toString()=class
    [Ljava.lang.Object;

    // // :( RexxReflectJava9.processMethod(), MethodType for Method [public 
final void
    
javafx.scene.chart.CategoryAxis.setCategories(javafx.collections.ObservableList)]:
    "(ObservableList)void"

    // // :( RexxReflectJava9.processMethod(): INSTANCE, 
mh.bindTo("CategoryAxis[id=xAxis,
    styleClass=axis]/class 
javafx.scene.chart.CategoryAxis").invokeWithArguments(...)

    // :) :) RexxReflectJava9.processMethod(), MethodHandle
    "MethodHandle(CategoryAxis,ObservableList)void" invocation caused a 
Throwable:
    java.lang.ClassCastException: java.base/[Ljava.lang.String; cannot be cast 
to
    java.base/java.lang.String


The supplied ObservableList argument represents the  month names and was 
created with the help of
"javafx.collections.FXCollections.observableList()" and then using its 
"addAll(monthNames)" method
to add the String array values returned by DateFormatSymbols.getMonths() to the 
list.

The supplied argument array "rru.funcArgs" will be coerced according to the 
reflected
"parameterTypes" array yielding the "coercedArgs" array; using 
java.util.Arrays.deepToString() gives:

    // // // RexxReflectJava9.processMethod(),
    coercedArgs=[[[Ljava.lang.String;@57cfe770]].getClass().toString()=class 
[Ljava.lang.Object;,
             parameterTypes=[interface
    javafx.collections.ObservableList].getClass().toString()=class 
[Ljava.lang.Class;:,
             
rru.funcArgs=[[[Ljava.lang.String;@57cfe770]].getClass().toString()=class
    [Ljava.lang.Object;

---

The story is much longer but after quite long debugging sessions, I turned on 
reflective invoke via
tmpMethod instead of invoking the corresponding MethodHandle, which 
(surprisingly) works.

Then, in the next step doing the same invocation via the corresponding 
MethodHandle immediately
after the reflective invocation, allows that invocation to execute successfully 
as well!

Please note, the supplied coerced argument is in both cases the same! Coercion 
occurs according to
the "parameterTypes" returned by java.lang.reflect.Method which also is used 
for creating the
MethodType in order to use a publicLookup.findVirtual(...). Or with other 
words: the coerced
argument will be identical for both invocation types!


Another strange observation in the success case is as follows: when using 
reflective invocation by
default (and then only invoking the MethodHandle in the special case that the 
method "setCategories"
is to be executed) the coerced argument supplied to 
java.util.Arrays.deepToString() will list the
monthnames:

    // // // RexxReflectJava9.processMethod(), ARRIVED: -> [INVOKE], 
tmpMethod=[public final void
    
javafx.scene.chart.CategoryAxis.setCategories(javafx.collections.ObservableList)]:
    method=[SETCATEGORIES] in
    
object=[rru.rexxArgs[1]="javafx.scene.chart.CategoryAxis@2d809949"/rru.bean="CategoryAxis[id=xAxis,
    styleClass=axis]"]

    // // // RexxReflectJava9.processMethod(), coercedArgs=[[January, February, 
March, April, May,
    June, July, August, September, October, November, December, 
]].getClass().toString()=class
    [Ljava.lang.Object;,
             parameterTypes=[interface
    javafx.collections.ObservableList].getClass().toString()=class 
[Ljava.lang.Class;:,
             rru.funcArgs=[[January, February, March, April, May, June, July, 
August, September,
    October, November, December, ]].getClass().toString()=class 
[Ljava.lang.Object;

    // // // RexxReflectJava9.processMethod(), bean=[CategoryAxis[id=xAxis, 
styleClass=axis]],
    methodName=[SETCATEGORIES], coercedArgs=[[January, February, March, April, 
May, June, July,
    August, September, October, November, December, ]]

    // // :( RexxReflectJava9.processMethod(), MethodType for Method [public 
final void
    
javafx.scene.chart.CategoryAxis.setCategories(javafx.collections.ObservableList)]:
    "(ObservableList)void"

    // // :( RexxReflectJava9.processMethod(): INSTANCE, 
mh.bindTo("CategoryAxis[id=xAxis,
    styleClass=axis]/class 
javafx.scene.chart.CategoryAxis").invokeWithArguments(...)

    ... add2cachedFieldsOrMethods(): rru.memberName=[SETCATEGORIES] ->
    rru.keyMemberName=[SETCATEGORIES], rru.invocationType=[INVOKE],
    
cfm=[CachedFieldOrMethod[mhk=METHOD,mh=MethodHandle(CategoryAxis,ObservableList)void,parameterTypes=[interface
    javafx.collections.ObservableList]]]

I double checked that the only difference is in using 
java.lang.reflect.Method.invoke(...) which
makes the Throwable on the method handle invocation go away (and the monthnames 
to be shown by
Arrays.deepToString()).

Here is the excerpt of the code section in question that allows the 
MethodHandle mh to be invoked
successfully with the same coerced argument:

    Class<?> parameterTypes[] = tmpMethod.getParameterTypes();
                    rru.result=tmpMethod.invoke(rru.bean,coercedArgs);      // 
java.lang.reflect.Method
    ... cut ...
                Class <?> returnType=tmpMethod.getReturnType();
                MethodType mt = MethodType.methodType(returnType, 
parameterTypes);
    ... cut ...
               // access via MethodHandle with the rights of 
rru.firstExportedClz
                            mh=thisLookup.findVirtual(rru.firstExportedClz, 
methodName, mt);
                            // mh=thisLookup.unreflect(tmpMethod);  // same 
behaviour

                    
rru.result=mh.bindTo(rru.bean).invokeWithArguments(coercedArgs); // bind to
    bean, invoke method

Just commenting out the reflective invocation in line 2 above
("rru.result=tmpMethod.invoke(rru.bean,coercedArgs); ") will cause the 
MethodHandle invocation to
fail with the reported Throwable!

--- 

Also it seems that java.util.Arrays.deepToString(...) is affected by this, if 
looking at the String
value it produces in both cases (different number of enclosing square brackets: 
the failing version
has another pair of enclosing square brackets).

Here the debug code for creating the above outputs ("coercedArgs" will be 
returned by a coerce
method and will be null, if the "rru.funcArgs" arguments from Rexx could not be 
coerced according to
the "parameterTypes"):

    String tmpStrCoercedArgs= (coercedArgs==null ? null :
    
Arrays.deepToString(coercedArgs)+".getClass().toString()="+coercedArgs.getClass().toString()
 );

    System.err.println("// // // RexxReflectJava9.processMethod(), 
coercedArgs="+tmpStrCoercedArgs
                                                                   +",\n\t\t
    
parameterTypes="+Arrays.deepToString(parameterTypes)+".getClass().toString()="+parameterTypes.getClass().toString()
                                                                   +":,\n\t\t
    
rru.funcArgs="+Arrays.deepToString(rru.funcArgs)+".getClass().toString()="+rru.funcArgs.getClass().toString()
                                                                   );

---

This strange observation is on Windows 7:

    F:\work\svn\bsf4oorexx\trunk\bsf4oorexx.dev\source_cc>java -version
    java version "9.0.1"
    Java(TM) SE Runtime Environment (build 9.0.1+11)
    Java HotSpot(TM) 64-Bit Server VM (build 9.0.1+11, mixed mode)

---

Would anyone have an idea what the reason could be or have any advice?

---rony

[1] Slides with the address book sample in question; look for the pictures in 
the section starting
at page: 
<http://www.rexxla.org/events/2017/presentations/AutoJava-BSF4ooRexx-07-JavaFx-201711.pdf>.

P.S.: Yes, the debug output could be cleaner (evolved from many permutations 
and formatting in the
past weeks), however this is right from the battle-field and tidying everything 
up is next on the
todo list.


Reply via email to