以前使ってみたNashornは割とJavaのスクリプト言語としてお手軽に使うことを重視しているといった印象を受けたが、Rhinoに関してはいろいろ細かいところに手が届きそうだ。Nashornではそのままではちゃんと実装できそうではなかったホストオブジェクトも実装できるようだ。ホストオブジェクトはブラウザでいう、window
やdocument
などのオブジェクトのことでJavaScript単体では実現できないような機能を提供する。document.title
を変更するとページのタイトルが変更できるがこれはホストオブジェクトがtitle
プロパティとページのタイトルを結びつける機能を提供しているのであって、素のJavaScriptのオブジェクト(ネイティブオブジェクト)のプロパティとは違うものだ。
JavaScriptのユニークなのはホストオブジェクトもネイティブオブジェクトのようにプロパティを追加できることだ。ブラウザで以下のようなコードを実行するとちゃんとmyprop
が変更できている。
document.myprop = "Hi";
alert(document.myprop);
NashornでもRhinoでもJavaのオブジェクトを渡すとホストオブジェクトのようにJavaで実装’された機能をJavaScriptから呼び出すことができる。以下のようなコードで実験してみた。RhinoはJDK 7に付属するものではなく https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino から取得したものを使っている。最終更新日は2013/2/24で大分前に更新が止まっているようだ。JDK 7のものではパッケージ名がsun.org.mozilla.javascript
になっているのに対し、MDNのものはorg.mozilla.javascript
になっている。
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
public class Greeting1 {
private String hello;
public Greeting1(String hello) {
this.hello = hello;
}
public String sayHello(String name) {
return hello + ", " + name + "!";
}
public static void main(String[] args) {
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
Greeting1 greeting = new Greeting1("Hello");
ScriptableObject.putProperty(scope, "greeting", greeting);
Object result = cx.evaluateString(scope, args[0], "<cmd>", 1, null);
System.out.println(Context.toString(result));
Context.exit();
}
}
以下をコマンドラインから実行すると、JavaScriptからJavaのコードが呼び出せるのがわかる。
CMD> java -cp js.jar;. Greeting1 "greeting.sayHello('Alice')"
Hello, Alice!
ところが、greeting.fooを変更しようとすると例外が発生してしまう。
CMD> java -cp js.jar;. Greeting1 "greeting.foo=123"
Exception in thread "main" org.mozilla.javascript.EvaluatorException: Java class
"Greeting1" has no public instance field or method named "foo". (<cmd>#1)
Rhinoでプロパティを追加できるホストオブジェクトを作るにはorg.mozilla.javascript.ScriptableObject
を継承したオブジェクトを作る必要がある。
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;
public class Greeting2 extends ScriptableObject {
private String hello;
private String secret;
public Greeting2() {
}
@JSConstructor
public Greeting2(String hello, String secret) {
this.hello = hello;
this.secret = secret;
}
@Override
public String getClassName() {
return "Greeting";
}
@JSFunction
public String sayHello(String name) {
return hello + ", " + name + "!";
}
public String saySecret(String name) {
return secret + ", " + name + "!";
}
public static void main(String[] args) throws Exception {
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
ScriptableObject.defineClass(scope, Greeting2.class);
Object[] initArgs = { "Hello", "Get away" };
Scriptable greeting = cx.newObject(scope, "Greeting", initArgs);
ScriptableObject.putProperty(scope, "greeting", greeting);
Object result = cx.evaluateString(scope, args[0], "<cmd>", 1, null);
System.out.println(Context.toString(result));
Context.exit();
}
}
以下をコマンドラインから実行すると、JavaScriptからプロパティの変更ができることがわかる。
CMD> java -cp js.jar;. Greeting2 "greeting.sayHello('Bob')"
Hello, Bob!
CMD> java -cp js.jar;. Greeting2 "greeting.foo=123; greeting.foo+456"
579
JavaScriptから見える関数にはアノテーションで指定する。sayHello()
には@JSFunction
がついているのでJavaScriptから見えるがsaySecret()
がついていないので触ることができない。
CMD> java -cp js.jar;. Greeting2 "greeting.saySecret('Bob')"
Exception in thread "main" org.mozilla.javascript.EcmaError: TypeError: Cannot find function saySecret in object [object Greeting]. (<cmd>#1)
もちろん、getClass()
とかもできない。
CMD> java -cp js.jar;. Greeting2 "greeting.getClass()"
Exception in thread "main" org.mozilla.javascript.EcmaError: TypeError: Cannot find function saySecret in object [object Greeting]. (<cmd>#1)
CMD> java -cp js.jar;. Greeting2 "Greeting.class"
undefined
JavaScriptオブジェクトを渡すとStringへ変換されるようだ。
CMD> java -cp js.jar;. Greeting2 "greeting.sayHello({ toString: function () { return 'Bob'; } })"
Hello, Bob!
JavaScriptオブジェクトをそのまま受け取るには引数の型にorg.mozilla.javascript.ScriptableObject
を指定する。
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;
public class Greeting3 extends ScriptableObject {
private String hello;
private String secret;
@JSConstructor
public Greeting3() {
}
@Override
public String getClassName() {
return "Greeting";
}
@JSFunction
public String sayObject(Object obj) {
return obj.getClass().toString();
}
@JSFunction
public String sayScriptableObject(ScriptableObject obj) {
return obj.getClass().toString();
}
public static void main(String[] args) throws Exception {
Context cx = Context.enter();
Scriptable scope = cx.initStandardObjects();
ScriptableObject.defineClass(scope, Greeting3.class);
Scriptable greeting = cx.newObject(scope, "Greeting");
ScriptableObject.putProperty(scope, "greeting", greeting);
Object result = cx.evaluateString(scope, args[0], "<cmd>", 1, null);
System.out.println(Context.toString(result));
Context.exit();
}
}
引数の型がObjectの場合はできるだけJavaの対応するクラスへ変換されるが、org.mozilla.javascript.ScriptableObject
の場合にはRhinoの内部オブジェクトが渡される。
CMD> java -cp js.jar;. Greeting3 "greeting.sayObject(123)"
class java.lang.Integer
CMD> java -cp js.jar;. Greeting3 "greeting.sayObject('Bob')"
class java.lang.String
CMD> java -cp js.jar;. Greeting3 "greeting.sayObject(new Date())"
class org.mozilla.javascript.NativeDate
CMD> java -cp js.jar;. Greeting3 "greeting.sayScriptableObject(123)"
class org.mozilla.javascript.NativeNumber
CMD> java -cp js.jar;. Greeting3 "greeting.sayScriptableObject('Bob')"
class org.mozilla.javascript.NativeString
CMD> java -cp js.jar;. Greeting3 "greeting.sayScriptableObject(new Date())"
class org.mozilla.javascript.NativeDate
CMD> java -cp js.jar;. Greeting3 "greeting.sayScriptableObject({})"
class org.mozilla.javascript.NativeObject
参考資料