5
$\begingroup$

I am trying to use Java reflection to access stateVector from the following package:

package org.yakindu.scr.lightswitch3;

public class Lightswitch3Statemachine implements ILightswitch3Statemachine {

protected class SCInterfaceImpl implements SCInterface {

    private boolean operate;

    public void raiseOperate() {
        operate = true;
    }

    protected void clearEvents() {
        operate = false;
    }
}

protected SCInterfaceImpl sCInterface;

private boolean initialized = false;

public enum State {
    main_region_off,
    main_region_on,
    $NullState$
};

private final State[] stateVector = new State[1];

This works well until I try to call setAccessible:

Needs["Jlink`"]
InstallJava[];
AddToClassPath[NotebookDirectory[] <> "lightswitch3.jar"];
m = JavaNew["org.yakindu.scr.lightswitch3.Lightswitch3Statemachine"];
sv = m@getClass[]@getDeclaredField["stateVector"];

This returns False, as expected:

sv@isAccessible[]

This fails:

sv@setAccessible[True]

with: Java: Method named setAccessible defined in class java.lang.reflect.Field was called with an incorrect number or type of arguments. The argument was True.

This also fails:

sv@setAccessible[MakeJavaObject[True]]

with: Java: Method named setAccessible defined in class java.lang.reflect.Field was called with an incorrect number or type of arguments. The argument was « JavaObject[java.lang.Boolean]».

Note that there is a static and non-static version of setAccessible:

Methods[sv, "setAccessible*"]

{
 {"void setAccessible(boolean) throws SecurityException"},
 {"static void setAccessible(java.lang.reflect.AccessibleObject[], \
boolean) throws SecurityException"}
}

So I also tried this:

list = JavaNew["java.util.ArrayList"];
list@add[MakeJavaObject[{sv}]];
java`lang`reflect`Field`setAccessible[list, MakeJavaObject[True]]

However it fails as well: Java: The static method javalangreflectFieldsetAccessible was called with an incorrect number or type of arguments. The arguments, shown here in a list, were {« JavaObject[java.util.ArrayList]»,« JavaObject[java.lang.Boolean]»}.

This issue may have something to do with this (unanswered) question: How to call a java method that takes a boolean (not Boolean)?

$\endgroup$
  • $\begingroup$ A very uneducated guess would be that there is a SecurityManager at play and it throws SecurityException, which JLink possibly interprets in the same way as if the method with this signature does not exist. It should be possible to test this theory by using a different classloader to load your class (which would not have a security manager attached). $\endgroup$ – Leonid Shifrin Dec 19 '17 at 20:58
  • $\begingroup$ I'm a complete novice at Java and JLink. How do I use a different class loader? $\endgroup$ – Todd Johnson Dec 19 '17 at 21:31
4
$\begingroup$

After reviewing this again How to call a java method that takes a boolean (not Boolean)?

I noticed that both setAccessible methods required a primitive boolean, not a Boolean object. Thus, all of the attempts above with MakeJavaObject[True] were destined to fail.

The following still failed:

java`lang`reflect`Field`setAccessible[list, True]

However, replacing the java object for the array (list) with {sv} worked:

java`lang`reflect`Field`setAccessible[{sv}, True]
sv@isAccessible[]
True

I'm not sure why {sv} works, when the array object does not. I'm still also not sure why sv@setAccessible[True] does not work.

For completeness, especially since I found nothing online on how to do this from MMA, here is code that shows reflection working. Note that this is simple two state machine (off, on) with a single action, operate, that toggles between the two states.

Needs["Jlink`"]
InstallJava[];
AddToClassPath[NotebookDirectory[] <> "lightswitch3.jar"];
m = JavaNew["org.yakindu.scr.lightswitch3.Lightswitch3Statemachine"];
sv = m@getClass[]@getDeclaredField["stateVector"];
java`lang`reflect`Field`setAccessible[{sv}, True];
m@init[];
m@enter[];

s1 = sv@get[m];
s1[[1]]@name[]

"main_region_off"

Raise the Operate action, which should turn the light on

m@raiseOperate[];
m@runCycle[];
s2 = sv@get[m];
s2[[1]]@name[]

"main_region_on"

Reset the machine back to s1 and test

sv@set[m, MakeJavaObject[s1]];
s3 = sv@get[m];
s3[[1]]@name[]

"main_region_off"

Raise operate again to be sure the machine still works with the forced state change

m@raiseOperate[];
m@runCycle[];
s4 = sv@get[m];
s4[[1]]@name[]

"main_region_on"
$\endgroup$
  • $\begingroup$ Interesting. I just checked using a different classloader that my theory was wrong, and was about to comment on that. $\endgroup$ – Leonid Shifrin Dec 19 '17 at 22:04
  • 1
    $\begingroup$ The setAccessible instance method fails because JLink has generated the wrong signature for it -- it appears to have copied the signature of the like-named static method by mistake. Perhaps JLink is getting confused because there are four overloads of that method in scope, two on Field and two on the superclass AccessibleObject. This looks like a bug to me. The methods on AccessibleObject have correct signatures. The ArrayList object fails because the signature requires a plain Java array, not a list object. $\endgroup$ – WReach Dec 19 '17 at 23:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.