Scripting: Javascript to Java Integration (Part 2)
Last time I discussed some of the main features of Javascript to Java integration in the Java 6 scripting engine. As you get into more advanced interaction with Java, however, there are some things you have to know to 'cross over'.
Implementing Interfaces
An important component of interacting with Java is the ability to implement Java interfaces. You can pass Javascript objects masquerading as Java objects into Java code, or even return the interface as a completed variable from the script.
There are multiple ways to implement an interface, and following the nature of Javascript, everything is very fluid and casual. The most implicit way to implement an interface is to create a new function (a javascript code block), and pass it in:
importPackage(java.lang);
t = new Thread(function() { println('hello') });
t.start();
The 'function' passed in is translated into a
java.lang.Runnable
by Rhino; this thread will be started and will execute hello. Yes, that means multi-threaded Javascript is possible.
You can also explicitly create a JS object, and associate nested functions with the appropriate methods of the interface. The Javascript implementation is closely related (syntactically) to a Java anonymous class. Here is how we create a Runnable object and implement the run method:
runnable = new Runnable() {
run: function() {
// Runnable code here.
}
};
The
run:
syntax tells Rhino you want the function to be an implementation of the run method of the interface.
This works well on multi-method interfaces too - let's consider this interface:
public interface MultiMethodInterface {
void method1();
void method2();
void method3();
}
The syntax is the same: for each method we want to implement, we simply declare the method to override, and set them up individually:
o = new MultiMethodInterface() {
method1: function() { println('method 1'); },
method2: function() { println('method 2'); },
method3: function() { println('method 3'); }
}
Note, however, that if you don't need to implement every function (imagine AWT/Swing MouseListeners for instance), you don't have to; instead you can implement just a few methods, and the rest will be left empty by Rhino:
o = new MultiMethodInterface() { method1: function() { println('method 1'); }, method2: function() { println('method 2'); } //method 3 not implemented }
In this case, method3 will do nothing if called, and if it had a return value, it would return null or 0. The corresponding Java implementation would look something like this:
Test.callMethods(new MultiMethodInterface() {
public void method1() {
System.out.println("method 1");
}
public void method2() {
System.out.println("method 2");
}
public void method3() { }
});
Note, however, if you use the implicit format from above, every method on the interface will be implemented with the function, so, given this method:
public static void callMethods(MultiMethodInterface methods) {
methods.method1();
methods.method2();
methods.method3();
}
If you call it from Javascript like this:
Test.callMethods(function() { println('method call') });
The string 'method call' would be printed three times (once for each method call). The implementation of this code in Java would look something like this:
Test.callMethods(new MultiMethodInterface() {
public void method1() {
System.out.println("method call");
}
public void method2() {
System.out.println("method call");
}
public void method3() {
System.out.println("method call");
}
});
Note that return values and method arguments are optional in Javascript, so if we were to change the interface like this:
public interface MultiMethodInterface {
void method1();
void method2(String arg);
String method3();
}
The javascript object we defined above will still work as expected. If you want to return a string value in method 3, you can however, and you can also define method two to take a value:
o = new MultiMethodInterface() {
method1: function() { println('method 1'); },
method2: function(arg) { println(arg); },
method3: function() {
println('method 3');
return 'method 3 result';
}
}
Pitfalls/Shortcomings
A standalone Rhino download ships with a bytecode generation library, and because of that it can do several things at runtime that normally aren't really possible. Standalone Rhino can allow a Javascript object to override abstract methods, implement multiple interfaces, and stand-alone Rhino can also build optimized class files out of your script code (referred to as the Rhino optimizer).
All that being said, the bytecode generation library was removed from the integrated Rhino, and the 'JavaAdapter' JS object was adapted as well, so be aware when reading up on Rhino on the internet; that component is changed. All that is left is what Java supports built in, which is the single interface implementation as described above, via Java dynamic proxies.
