北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

Rhino事始め - JavaとRhinoのコラボ(2)

2. 一つの関数を何度も実行させる


ここからは今までと少しだけ趣向が変わってきます。今まではJavaScript処理を直接書いて即時実行させていましたが、ここからは処理の定義だけ記述してJavaからそれをキックすると言うアプローチを取ります。


JavaScriptでは関数を定義できますので、それをJavaからキックしてみましょう。greetings.jsとして、hello()とbye()をJavaScriptに定義します。

function hello(obj){
	 java.lang.System.out.println(obj + ", hello!");
}

function bye(obj){
	 java.lang.System.out.println(obj + ", good-bye!");
}


では、Java側を用意します。今回はちょっとJavaっぽく、init(前処理)、run(処理)、close(後処理)のメソッドを持つ以下のようなクラスを用意します。

package jstest;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

public class Greetings {
    static final File SCRIPT = new File("greetings.js");

    /* 保持するJavaScriptの情報 */
    private Context    cx        = null;
    private Scriptable global    = null;
    private Function   helloFunc = null;
    private Function   byeFunc   = null;

    /* このクラスを動かすメインルーチン */
    public static void main(String[] args){
        Greetings greetings = new Greetings();

        try{
            greetings.init();
            greetings.run();
        }finally{
            greetings.close();
        }

    }

... 以下、メソッドの定義 ...

}

*1


これでGreetingsインスタンスRhinoエンジンのライフサイクルを管理しやすくなります。close()を呼んだ後にGreetingsインスタンスを速やかに破棄すれば、Rhinoエンジンが終了しているのにGreetingsの機能を呼んでしまったりすると言う事故がなくなります。。


次に、init()関数とclose()を書きます。init()ではスクリプトを実行することによって関数定義を生成し、それを変数helloFuncとbyeFuncに保持しています。close()はexit()を呼んでJavaScriptのリソースを開放するだけです。

    public void init(){
        /* エンジンの初期化とグローバルスコープの準備 */
        cx     = Context.enter();
        global = cx.initStandardObjects();

        /* スクリプトの実行(関数の生成) */
        FileReader r = new FileReader(SCRIPT);
        cx.evaluateReader(global, r, SCRIPT.getAbsolutePath(), 1, null);
        r.close();

        /* 関数を取り出す */
        helloFunc = (Function)ScriptableObject.getProperty(global, "hello");
        byeFunc   = (Function)ScriptableObject.getProperty(global, "bye");
    }


    public void close(){
        Context.exit();
    }

スコープからオブジェクトを取り出すのにScriptableObjectのstaticメソッドを使っています*2。戻り値は例のごとくObjectなので、適切にキャストする必要があります。


最後、関数が取り出されたら後は実行するだけ。run()はこんな感じです。

    public void run(){
        Object[] params = new Object[] {"hiratara"};

        helloFunc.call(cx, global, global, params);
        byeFunc.call(cx, global, global, params);
    }

call()が引数が多くて恐ろしく見えますが、JavaScriptの関数に渡している実際の引数はparams配列の中身となります。ここでは文字列を一つだけ渡しています。なお、callの第一引数はコンテクスト、第二引数はスコープオブジェクト、第三引数はthisをあらわすオブジェクトとなります。

[実行結果]
hiratara, hello!
hiratara, good-bye!


例のごとくgreetings.jsをいじれば簡単に実行結果を変えられます。お試しあれ。


次回はようやく、JavaScriptJavaのインタフェースを実装します。




ちなみに、ScriptableObject.callMethod()と言う便利なメソッドもあります。こいつは名前から関数オブジェクトを検索¥x{ff5e}実行までをまとめてやってくれるメソッドです。今回は関数オブジェクトを保持したかったので使っていません。

*1:例外処理は書いてないので注意。

*2:Scriptable#get()よりわかりやすくていいと思う