I'm using the Mozilla Rhino JavaScript emulator. It allows me to add Java methods to a context and then call them as if they were JavaScript functions. But I can't get it to work except when I use a static method.
The problem is this part of the documentation:
If the method is not static, the Java 'this' value will correspond to the JavaScript 'this' value. Any attempt to call the function with a 'this' value that is not of the right Java type will result in an error.
Apparently, my Java "this" value doesn't correspond with the one in JavaScript and I have no idea how to make them correspond. In the end, I'd like to create an instance in Java, and install a couple of methods from it in the global scope, so I can initialize the instance from Java but use it in my scripts.
Does anyone have some example code for this?
What you can do is to bind a Java instance to the Javascript context, and then from Javascript that identifier will be a reference to the "real" Java object. You can then use it to make method calls from Javascript to Java.
Java side:
Javascript:
Now that example assumes you're using the JDK 6 java.util.script APIs to get between Java and Rhino. From "plain" Rhino, it's a little different but the basic idea is the same.
Alternatively, you can import Java classes into the Javascript environment, and Rhino gives you Javascript-domain references to Java objects when you use Javascript "new" on references to Java classes.
When a java method (whether static or non-static) is to be made available as a global function within a scope we use the following logic:
Here the
boundScope
should always be the scope in which the function is to be made available.However the value of the parent scope depends on whether we are binding an instance method or static method. In case of a static method, it can be any scope that makes sense. It can even be the same as the
boundScope
.But in case of instance method, the
parentScope
should be the instance whose method is being bound.The above was just background info. Now I will explain what the issue is and give a natural solution for it i.e. one that allows invoking the instance method directly as a global function rather than explicitly creating an instance of the object and then invoking the method using that instance.
When a function is called, Rhino invokes the
FunctionObject.call()
method that is passed a reference tothis
. In case the function is a global function, it is called without a reference tothis
(i.e.xxx()
instead ofthis.xxx()
), the value of thethis
variable that gets passed to theFunctionObject.call()
method is the scope in which the call was made (i.e. in this case the value of thethis
parameter will be same as the value of thescope
parameter).This becomes a problem in case the java method being invoked is an instance method because as per the JavaDocs of constructor of
FunctionObject
class:If the method is not static, the Java
this
value will correspond to the JavaScriptthis
value. Any attempt to call the function with athis
value that is not of the right Java type will result in an error.And in the scenario described above that is exactly the case. The javascript
this
value does NOT correspond to the javathis
value and results in an incompatible object error.The solution is to subclass
FunctionObject
, override thecall()
method, forcefully 'fix' thethis
reference, and then let the call proceed normally.So something like:
I think it would be best understood with a self-contained/complete example pasted below. In this example, we are exposing the instance method: myJavaInstanceMethod(Double number) as a global function within a javascript scope ('scriptExecutionScope'). So in this case the value of the 'parentScope' parameter must be an instance of the class that contains this method (i.e. MyScriptable).
If you want to see the behavior WITH the fix then uncomment line 78 and comment line 79:
If you want to see the behavior WITHOUT the fix then comment line 78 and uncomment line 79:
Hope this helps.