Monday, September 30, 2019 / JavaScript, Rhino, Groovy

Rhino を使って Java から JavaScript を実行する

今更な話題ですが 将来 nashorn (JEP 335: Deprecate the Nashorn JavaScript Engine ) が廃止になるらしい.
nashorn の代わりに Rhino で実行する方法を調査したのでメモします.

実行する javascript から Java の自前オブジェクトを使いたい

MDN web docs Tutorial: Embedding Rhino を読めばだいたいわかります。

Java側でJavaScriptで使うことを想定して用意したオブジェクトは JavaScript host objects とよばれているようです.

ここでは JavaScript から使う Pikachu オブジェクトを Java側で用意する例を考えます. pikachu.say すると pika と発言するようにしましょう.

Pikachu クラス

Java 側( コードは groovy です) で用意します.

import org.mozilla.javascript.Context
import org.mozilla.javascript.ScriptableObject

public class Pikachu extends ScriptableObject {
    @Override
    String getClassName() { return 'Pikachu' }

    public void jsConstructor() {}
    public String jsGet_say(){ return 'pika' }
    public String jsFunction_sayHello(int i){ return 'pika ' * i }
}

これを

def cx = Context.enter()
def scope = cx.initStandardObjects()
ScriptableObject.defineClass(scope, Pikachu.class)

で用意しておき

def result = cx.evaluateString(scope, 'new Pikachu().say', "<cmd>", 1, null)

で JavaScript new Pickachu().say を評価します. result には pika が入ります.

まとめ

コード全体では以下のようになります.

pikachu.groovy

@Grab(group='org.mozilla', module='rhino', version='1.7.11')
import org.mozilla.javascript.Context
import org.mozilla.javascript.ScriptableObject

class Pikachu extends ScriptableObject {
    @Override
    String getClassName() { return "Pikachu"; }

    void jsConstructor() {}
    String jsGet_say(){ return 'pika' }
    String jsFunction_sayHello(int i){ return 'pika ' * i }
}


def cx = Context.enter()
def scope = cx.initStandardObjects()

def arg = new Object[0];
ScriptableObject.defineClass(scope, Pikachu.class)

def script0 = 'new Pikachu().sayHello(3);'
def result0 = cx.evaluateString(scope, script0, "<cmd>", 1, null)
println result0;

groovy pikachu すれば

$ groovy main
pika pika pika

になります.

あらかじめ、Pikachu クラスのインスタンス pikachu を グローバル(トップレベル)に存在させておくようにするには、Java側で以下の記述を追加しておけばOKです.

def arg = new Object[0];
def pikachu = cx.newObject(scope, "Pikachu", arg)
scope.put("pikachu", scope, pikachu)