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

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

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

いくらAjaxが流行ってもJavaScriptよりJavaの方が楽しい・・・なんて自分のために、JavaをメインとしたRhinoの使い方をまとめておきます。ここから情報源ががくっと減るのはRhinoがあまり流行ってないからでしょうか・・・。


JavaからちょくちょくRhinoを呼ぶ手法の例として、以下の三つを実装してみます。

  1. 一つのスクリプトを何度も実行する
  2. 一つの関数を何度も実行させる
  3. Javaのインタフェースを実装する

1. 一つのスクリプトを何度も実行

発生したイベントに対してJavaScriptを実行するようなパターンがこの例です。ここでは、一定時間毎にJavaScriptを実行することで、単純にカウントを行うプログラムを作ります。

まず、interval.jsを以下のように用意します。これは、Javaから呼ばれて時刻を表示するJavaScriptです。なお、このアプリのJavaScriptでは、hour, min, secと言う変数が利用可能であると言う仕様にします。

var time = hour + ":" + min + ":" + sec;
if(sec == 0){
	java.lang.System.out.println("ぴったり" + time + "です。");
}else{
	java.lang.System.out.println(time + "です。");
}


こいつを1秒毎に呼び出すJavaを書いてみます。現在時刻はJava側で取得し、hour, min, secと言う変数をスクリプトから見えるようにセットしてやります。Javaのコードは以下です。

package jstest;

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

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

public class Interval {
    public static void main(String[] args)
    throws IOException, InterruptedException{
        File       script = new File("interval.js");
        Context        cx = Context.enter();

        try{
	    /* ファイルからスクリプトをコンパイル */
            FileReader r = new FileReader(script);
            Script compiled = cx.compileReader(r, 
                                         script.getAbsolutePath(), 1, null);
            r.close();

	    /* グローバルスコープの初期化 */	   
            Scriptable global = cx.initStandardObjects();

            while(true){
		/* カレンダー取得 */
                Calendar cal = Calendar.getInstance();

		/* 時分秒の取り出し */
                Integer hour = Integer.valueOf(cal.get(Calendar.HOUR_OF_DAY));
                Integer min  = Integer.valueOf(cal.get(Calendar.MINUTE));
                Integer sec  = Integer.valueOf(cal.get(Calendar.SECOND));

		/* JavaScriptにラップ */
                Object jsHour = Context.javaToJS(hour, global);
                Object jsMin  = Context.javaToJS(min,  global);
                Object jsSec  = Context.javaToJS(sec,  global);

		/* グローバルのプロパティにセットする */
                ScriptableObject.putProperty(global, "hour", jsHour);
                ScriptableObject.putProperty(global, "min",  jsMin);
                ScriptableObject.putProperty(global, "sec",  jsSec);

		/* スクリプトを実行 */
                compiled.exec(cx, global);

                Thread.sleep(1000);
            }
        }finally{
            Context.exit();
        }
    }
}

/*
[実行結果]
18:59:55です。
18:59:56です。
18:59:57です。
18:59:58です。
18:59:59です。
ぴったり19:0:0です。
19:0:1です。
19:0:2です。
19:0:3です。
*/


まず、compileReader()とScript#exec()を使っているところが今までと違います。コンパイルフェーズと実行フェーズを分けることで、2回目以降の呼び出しで余分なファイル読み込み〜コンパイルを行わずに高速に実行させることができます。


後、javaToJs()とputProperty()。これでスクリプト側の環境を整えてやることで、スクリプトの記述を簡素化することができます。


では、せっかくなのでJavaScriptの柔軟性を確かめるために、interval.jsだけを書き換えて機能を修正してみます。現在時刻ではなく実行してから何回目の呼び出しかを表示するように変更します。interval.jsを以下のようにしました。

var count;
if(count == null) count = 0;
java.lang.System.out.println( (++count) + "回目のコールです。");

/*
[結果]
1回目のコールです。
2回目のコールです。
3回目のコールです。
4回目のコールです。
5回目のコールです。
*/

countが2回目以降の呼び出しも消去されずに残っています。これはグローバルスコープの初期化が一回だけであるためです。varは変数を宣言するだけで、中身の初期化は行いません。