先週はスクリプトとJavaの間でオブジェクトの共有を行なう方法を解説しました。

グローバルスコープとエンジンスコープ

スクリプトとJavaでオブジェクトの共有を行うにはBindingsインタフェースを使います。BindingオブジェクトにはScriptEngineクラスのputメソッド、getメソッドを用いてアクセスします。このように書くと、Bindingsオブジェクトはスクリプトエンジンにつき1つのようですが、ちょっと違うのです。

今まで使用してきたBindingsオブジェクトはスクリプトエンジンが保持しています。この他にスクリプトエンジンマネージャが保持しているBindingsオブジェクトがあるのです。

この2つのBindingsオブジェクトをまとめているのが、javax.script.ScriptContextインタフェースです。

今まではスクリプトエンジンが直接Bindingsオブジェクトを保持しているように記述していました。しかし、実際はスクリプトエンジンはScriptContextオブジェクトを保持しており、ScriptContextオブジェクトがBindingsオブジェクトを保持しているのです。これを表したのが図1です。

ScriptContext
図1 ScriptContext

スクリプトエンジンマネージャで管理し、すべてのスクリプトエンジンに対しても適用されるものをグローバルスコープといいます。その一方、個々のスクリプトエンジンが管理し、適用するスコープをエンジンスコープといいます。

Bindingsオブジェクトもこのスコープで区別されます。今までのサンプルで扱ってきたのはすべてエンジンスコープのBindingsオブジェクトです。

それではグローバルスコープのBindingsオブジェクトとエンジンスコープのBindingsオブジェクトで同じキーを使っていたらどうなるのでしょうか。サンプルで試してみましょう。

サンプルのソース BindingSample5.java

グローバルスコープのBindingsオブジェクトには、ScriptEngineManagerクラスのputメソッド、getメソッドを使用してアクセスします。

    public BindingSample5() {
        ScriptEngineManager manager = new ScriptEngineManager();
 
        // グローバルスコープのバインディング
        manager.put("date", "DATE");
 
        ScriptEngine engine = manager.getEngineByName("js");
         
        // エンジンスコープのバインディング
        Date now = new Date();
        engine.put("date", now);
         
        try {
            engine.eval(SCRIPT);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
    }

青字がグローバルスコープ、赤字がエンジンスコープのBindingsオブジェクトにアクセスしている部分です。

グローバルスコープはdateに対して"DATE"という文字列、エンジンスコープでは現在時刻を保持させています。

スクリプトは今まで同じく、単に変数を出力しているだけです。これを実行させたらどうなるでしょう。

C:\scripting>java BindingsSample5
Thu Feb 21 23:36:56 JST 2008

どうやら、エンジンスコープのBindingsオブジェクトが優先されるようです。

グローバルスコープのBindngsオブジェクトを使用すると、複数のスクリプト間でオブジェクトを共有することができます。

サンプルのソース BindingSample6.java

このサンプルではJavaとJavaScript、そしてJRubyでオブジェクトを共有します。

    private String JS_SCRIPT = "println('JS: ' + date);";
    private String RUBY_SCRIPT = "puts 'Ruby: ' + $date.to_s";
 
    public BindingSample6() {
        ScriptEngineManager manager = new ScriptEngineManager();
        Date date = new Date();
        System.out.println("Java: " + date);
 
        // グローバルスコープのバインディング
        manager.put("date", date);
 
        // Rhinoで実行
        ScriptEngine jsEngine = manager.getEngineByName("js");
        try {
            jsEngine.eval(JS_SCRIPT);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
 
        // JRubyで実行
        ScriptEngine rubyEngine = manager.getEngineByName("ruby");
        try {
            rubyEngine.eval(RUBY_SCRIPT);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
    }

BindingSample5クラスと同様に現在時刻をグローバルスコープのBindingsオブジェクトに保持させます(赤字部分)。

Rubyではグローバル変数は変数名の前に$を付加します。また、文字列に変更するためto_sメソッドをコールしています。to_sメソッドは、JRubyスクリプトエンジンの内部でtoStringメソッドに委譲されます。

さて、これを実行してみましょう。ただしく実行できれば、同じオブジェクトを3種類の言語からアクセスできるはずです。

ここでは、JRubyはカレントディレクトに配置しました。詳しくは再来週に解説しますが、JRubyスクリプトエンジンを起動する場合、com.sun.script.jruby.loadpath環境変数をセットする必要があります。

C:\scripting>java -Dcom.sun.script.jruby.loadpath=jruby
\lib\jruby.jar -cp jruby\lib\*;jruby\build\jruby-engine.jar;
. BindingSample6
Java: Sat Feb 21 23:47:02 JST 2008
JS: Sat Feb 21 23:47:02 JST 2008
Ruby: Sat Feb 21 23:47:02 JST 2008

このようにグローバルスコープとエンジンスコープは用途によって使い分けることで、いろいろおもしろいことができそうですよ。

さて、今週まで7回に渡ってJava SE 6に標準で提供されてきたRhinoスクリプトエンジンを主に使用してきました。来週から他のスクリプト言語を扱っていきます。お楽しみに。

 

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

櫻庭が使用しているデジカメはニコンのD70です。もうすぐ、丸4年になります。このカメラで撮った写真は8万枚を超えます。

さすがにこれだけ使っていると、いろいろとガタが来るものですね。ボディの傷はいたるところ、ファインダー内のゴミや、コンパクトフラッシュとの接点不良など、など。

でも、一番心配なのはシャッターユニットが壊れてしまうのではないかということ。このカメラのシャッターはメカニカルシャッター。つまり、稼動部がとても多いということです。ということは、壊れやすいわけですね。内心ひやひやしながら、写真を撮り続ける日々です。