J2SE6と式評価エンジン

拙作のフレームワークではDIコンテナの評価式のエンジンを挿げ替えることができるようになっているが、Javaでもこの仕様を踏襲する予定だ。
過去記事一覧検索 "DI 式評価"

.NETでは、Jscript.NET、IronPython、PoweShellと試してきたが、JavaではJ2SE6が提供しているJavaTM プラットフォームのスクリプティング機能がおあつらえ向きなので、この機能と統合しようと思っている。

J2SE6がホストするJavascriptエンジンの実装はMozilla Rhino:Java Script for Javaだ。手始めにこいつを使って.NETの時と同様に評価式インタフェースの実装を書いてみる。

IEvaluatorインタフェースはC#の時と同じで、違うのはスクリプトエンジンに渡すパラメタと引数の辞書をMapにしているだけだ。

public interface IEvaluator {
    Object evaluate(String exp, Map ctx, Object root);
    Object evaluate(String exp, Object root);

}

以下はJ2SE6のjavascriptエンジンに合わせたIEvaluatorの実装クラス。

public class RhinoJavascriptEvaluator implements IEvaluator {
    public static final IEvaluator Instance = new RhinoJavascriptEvaluator();
    private static final ScriptEngineManager scriptEngineMgr 
            = new ScriptEngineManager();
    private static final ScriptEngine scriptEngine 
            = scriptEngineMgr.getEngineByName("JavaScript");
    private static CompiledScript compiledScript;

    private static final String EVAL_FUNCTION = "eval(expr);";

    static {
        if (scriptEngine != null) {
            try {
                Compilable compilable = (Compilable)scriptEngine;
                compiledScript = compilable.compile(EVAL_FUNCTION);
 
            } catch (ScriptException ex) {
                compiledScript = null;
            }
        }
    }
    @Override
    public Object evaluate(String expr, Map ctx, Object root) {
        try {
            Bindings bindings 
                = scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE );
            if ( ctx != null ) {
                for (String key : ctx.keySet()) {
                    bindings.put(key, ctx.get(key));
                }
            }
            bindings.put("root", root);
            bindings.put("expr", expr);
            if ( compiledScript != null ) {
                return compiledScript.eval(bindings);
            } else {
                return scriptEngine.eval(EVAL_FUNCTION, bindings);
            }
        } catch (ScriptException ex) {
            return expr;
        }
    }
    @Override
    public Object evaluate(String expr, Object root) {
        return this.evaluate(expr, null, root);
    }
}

例のごとく、先ほど思いついて実装したのでまだ内容は穴だらけだと思うが、一応は動く。
スタティックコンストラクタを見れば解るが、評価式を評価するスクリプトをコンパイルしており、これにより繰り返し実行するスクリプトを効率良く実行できる。

Compilable compilable = (Compilable)scriptEngine;
compiledScript = compilable.compile(EVAL_FUNCTION);

Compilableを実装していれば「コンパイル可能」ということになるのだが、Rhinoはスクリプトのコンパイルをサポートしているようだ。

Javaの世界からJavascriptの世界にはパラメタをバインドすることができ、スクリプト内でパラメタを参照できる。

Bindings bindings = scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE );
if ( ctx != null ) {
    for (String key : ctx.keySet()) {
        bindings.put(key,ctx.get(key));
    }
}
bindings.put("root", root);
bindings.put("expr", expr);

さて、実装できたので試してみよう。
設定ファイルには.NETと同様に評価式の要素にはJavascriptのエンジンを使うことを明示する要素を使って、コンストラクタからのインジェクションとして書いてみよう。


    new Date().toString()

今回のフレームワークはSwing向けなので今後、テストにはGUIのクラスを使っていく。今回はJTextFieldを使用し、これをFrameの上に配置して実行した結果が以下のスクリーンショットだ。

見事にJTextFieldにjavascriptのDateクラスオブジェクトの文字列表現がセットできた。実際には内部でJavascriptJavaの文字列の変換が発生しているが表示上特に問題は発生していない。
なお、この場合内部では

eval(new Date().toString());

というスクリプトが実行されているだけである。

Rhinoの実装はJavascript中であってもJavaのオブジェクトを直接作ることもできる。今度は設定ファイルを以下のように書き換えてみよう。


    new java.text.SimpleDateFormat("yyyy'年'MM'月'dd'日'").format(new java.util.Date());


とこのように、まだ少ししか試していないがRhinoの実装はJavaとの親和性も高く、考えていることの殆どができそうだ。