今月も引き続きスクリプト言語のサポートに関して解説していきます。

今週はJavaからスクリプトの関数の呼び出しについて取りあげます。

関数の呼び出し

先週、解説した手法はスクリプトの先頭からそのまま評価していきます。しかし、イベントが発生したときに特定のコールバック関数を呼ぶなど、関数単位でスクリプトを呼び出したいことがよくあります。

このような場合、javax.script.Invocableインタフェースを使用します。

スクリプトエンジンの実装で関数呼び出しに対応しているものはInvocableインタフェースを実装しています。Invocableインタフェースを実装することはオプションなので、関数呼び出しに対応していなければ実装する必要はありません。

しかし、ScriptEngineクラスにはInvocableインタフェースを実装しているかどうか調べるためのメソッドは用意されていないのです。そのため、ScriptEngineオブジェクトに対しinstanceof、もしくはリフレクションを使って調べる必要があります。

サンプルのソース IsInvocableScriptEngine.java

このサンプルでは、instanceofを使用してスクリプトエンジンがInvocableインタフェースを実装しているか調べています。

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
 
public class IsInvocableScriptEngine {
    private static void isInvocable(ScriptEngine engine) {
        // Invocableインタフェースを実装しているかチェックする
        if (engine instanceof Invocable) {
            System.out.println(engine.getFactory().getEngineName()
                               + " is Invocable");
        } else {
            System.out.println(engine.getFactory().getEngineName()
                               + " is NOT Invocable");
        }
    }
 
    public static void main(String[] args) {
        // スクリプトエンジンマネージャの生成
        ScriptEngineManager manager = new ScriptEngineManager();
 
        if (args.length > 0) {
            // スクリプトエンジンの取得
            ScriptEngine engine = manager.getEngineByName(args[0]);
             
            if (engine != null) {
                isInvocable(engine);
            } else {
                System.out.println("No Such ScriptEngine: " + args[0]);
            }
        } else {
            System.out.println(
            "Usage: java IsInvocableScriptEngine [ScriptEngine Name]");
        }
    }
}

赤字で示している部分が、Invocableインタフェースを実装しているか調べている部分です。

ここではRhinoとJavaFX、JRubyを調べてみました。もちろん、JavaFXとJRubyは標準では組み込まれていないので、別途インストールを行なっています。

C:\scripting>java IsInvocableScriptEngine js
Mozilla Rhino is Invocable
 
C:\scripting>java IsInvocableScriptEngine FX 
JavaFXEngine is NOT Invocable
 
C:\scripting>java IsInvocableScriptEngine jruby 
jruby is Invocable

RhinoスクリプトエンジンとはJRubyスクリプトエンジンはInvocableインタフェースを実装していますが、JavaFXスクリプトエンジンはInvocableを実装していないことが分かります。

つまり、RhinoスクリプトエンジンとJRubyスクリプトエンジンでは関数呼び出しが可能ということです。JavaScriptの場合を、サンプルで試してみましょう。

サンプルのソース InvokeFunctionSample.java

本来はInvocableインタフェースを実装しているかどうかのチェックをソース中に記述するべきですが、ここでは簡略化のため省略させていただきました。

さて、InvokeFunctionSampleクラスではスクリプトをクラスの中で定義しています。

    public final static String SCRIPT
        = "function hello(names) {"
        + "    for (i in names) {"
        + "        print('Hello, ' + names[i] + '\\n');"
        + "    }"
        + "}";

もちろん、ファイルからスクリプトを読み込むのでも、まったくかまいません。

スクリプトエンジンを取得する部分までは、先月解説した方法と同一です。

    public InvokeFunctionSample() {
        // スクリプトエンジンマネージャの生成
        ScriptEngineManager manager = new ScriptEngineManager();
        // スクリプトエンジンの取得 (JavaScript)
        ScriptEngine engine = manager.getEngineByName("js");
 
        try {
            // スクリプトの評価
            engine.eval(SCRIPT);

            // 関数の呼び出し
            ((Invocable)engine).invokeFunction("hello", 
                (Object)(new String[]{"World", "Java", "JavaScript"}));
        } catch (NoSuchMethodException ex) {
            System.err.println("関数がありません");
            System.err.println(ex.getMessage());
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
    }

関数呼び出しを行なう場合でも、一度スクリプトを評価することが必要です。そこで、青字に示したようにevalメソッドをコールします。evalメソッドで評価したとしても、スクリプトには関数しか定義していないので、何も起りません。

評価できたら、関数の呼び出しが可能になります。

赤字で示した部分が関数の呼び出しの処理です。関数の呼び出しはInvocableインタフェースのinvokeFunctionメソッドを使用します。

Invocableオブジェクトして扱うため、ScriptEngineオブジェクトをInvocableインタフェースにキャストします。その後、invokeFunctionメソッドをコールします。

invokeFunctionメソッドは第1引数が関数名、第2引数以降が関数への引数になります。上記のコードで、引数をObjectクラスにキャストしてあるのは、第2引数以降が可変長引数になっているためです。

可変長引数は内部的にはObjectクラスの配列として扱われます。このため、配列を引数に指定した場合、単一の引数として配列を扱うのか、可変長の引数として配列を扱うのか判別ができません。ここでは、単一の引数として扱うため、Objectクラスにキャストを行なっているのです。

もし、可変長引数として扱うのであれば、Objectクラスの配列、つまりObject[]でキャストします。

invokeFunctionメソッドはScriptEnception例外以外に、NoSuchMethodException例外もスローするので、例外処理が必要です。

では、実際に実行してみましょう。

C:\scripting>java InvoceFunctionSample
Hello, World
Hello, Java
Hello, JavaScript

関数の呼び出しができることが確認できます。

メソッドの呼び出し

上述の方法で関数の呼び出しは行なうことができました。では、オブジェクト指向言語におけるオブジェクトへのメッセージパッシング、つまりメソッドコールはどうすればいいのでしょうか。

オブジェクトに対するメソッドコールもInvocableインタフェースを使用して実現します。

サンプルのソース InvokeMethodSample.java

まずはスクリプトです。前のサンプルと同様、InvocableMethodSampleクラスもクラス中にスクリプトを定義しています。

    public final static String SCRIPT
        = "var obj = {"
        + "    hello: function(names) {"
        + "        for (i in names) {"
        + "            print('Hello, ' + names[i] + '\\n');"
        + "        }"
        + "    }"
        + "}";

このスクリプトでは、オブジェクトにhelloメソッドを定義しています。

メソッドの呼び出しは関数の呼び出しとほとんど同じですが、オブジェクトの指定が必要です。

    public InvokeMethodSample() {
        // スクリプトエンジンマネージャの生成
        ScriptEngineManager manager = new ScriptEngineManager();
        // スクリプトエンジンの取得 (JavaScript)
        ScriptEngine engine = manager.getEngineByName("js");
 
        try {
            // スクリプトの評価
            engine.eval(SCRIPT);
            
            // オブジェクトの取得
            Object obj = engine.get("obj");

            // メソッドの呼び出し
            ((Invocable)engine).invokeMethod(obj, "hello", 
                (Object)(new String[]{"World", "Java", "JavaScript"}));
        } catch (NoSuchMethodException ex) {
            System.err.println("関数がありません");
            System.err.println(ex.getMessage());
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
    }

スクリプトのグローバル変数は、Javaからアクセスすることができます。詳しくは来月解説しますが、ScriptEngineクラスのgetメソッドでスクリプトのグローバル変数にアクセスできます。

getメソッドの引数はグローバル変数の変数名です。このサンプルのスクリプトではobjという変数を使用しているので、赤字で示したようにgetメソッドの引数は"obj"となります。

取得したスクリプト変数に対してメソッドコールをするには、InvocableインタフェースのinvokeMethodメソッドを使用します。関数を呼び出すときはinvokeFunctionメソッドで、メソッドを呼び出すときはinvokeMethodなので、覚えやすいですね。

invokeMethodメソッドの第1引数はスクリプトのオブジェクト、第2引数はメソッド名、第3引数以降がメソッドへの引数となります。ここでも、第3引数以降は可変長引数になるので、配列をスクリプトのメソッドの引数にするときにはキャストを行ないます。

では、これで実行してみましょう。

C:\scripting>java InvokeMethodSample
Hello, World
Hello, Java
Hello, JavaScript

正しく実行することができました。

このようにして、スクリプトに対して関数やメソッドの呼び出しができること分かりました。しかし、この方法はいうなればリフレクションのように間接的に関数もしくはメソッドにアクセスしています。

そこで、来週はもう少し直接的に扱うために、インタフェースを介して関数もしくはメソッドにアクセスする方法を紹介します。お楽しみに。

 

著者紹介 櫻庭祐一

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

今月の櫻庭

Colors, Patrick Roger, Salon du Chocolat Tokyo, Shinjuku Isetan
パトリック ロジェのショコラ

先月、東京の新宿でショコラの祭典Salon du Chocolatが行われました。今月にかけて京都、名古屋、小倉でも開催されています。

ショコラが大好きな櫻庭がこれに行かないわけがありません。

ショコラといってもいろいろな種類があるわけですが、特にボンボンショコラが大好き。外側のコーティングがすっと解けて、中のガナッシュの香りが口の中に広がっていくのです。まさに至福。

Salon du Chocolatにはさまざまなチョコレート職人(ショコラティエ)が参加していて、どれを買おうか迷ってしまいがち。そんなとき、どれを買えばいいか目安になるのがMOFやルレ・デセールです。

MOF (Meilleur Ouvrier de France)はフランスの国家最優秀職人賞のことで、さまざまな分野での第一人者に与えられる称号です。ショコラティエでは14人がMOFを獲得しています。

ルレ・デセール(Relais Desserts)はパティシエおよびショコラティエの情報交換を目的に作られた組織です。ルレ・デセールには日本人も参加しており、2007年にはサダハルアオキ氏がルレ・デセールの会員に認定されています。

つまり、MOFを獲得しているショコラティエ、ルレ・デセールの会員であるショコラティエであれば、はずれはないということです。

今年、櫻庭が購入したチョコレートでも、MOF獲得者であるパトリック ロジェ(Patrick Roger)のチョコレートがとても印象的でした。

ぜひ、参考にしてみてください。