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(); } } ... 以下、メソッドの定義 ... }
これで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をいじれば簡単に実行結果を変えられます。お試しあれ。
次回はようやく、JavaScriptでJavaのインタフェースを実装します。
ちなみに、ScriptableObject.callMethod()と言う便利なメソッドもあります。こいつは名前から関数オブジェクトを検索¥x{ff5e}実行までをまとめてやってくれるメソッドです。今回は関数オブジェクトを保持したかったので使っていません。