先週はJavaからスクリプトの関数もしくはメソッドを呼び出す方法を解説しました。

その手法はスクリプトを評価してから、javax.script.InvocableインタフェースのinvokeFunctionメソッド、もしくはinvokeMethodメソッドを使用して間接的に呼び出すというものでした。

今週はその続きで、インタフェースを使って、ダイレクトにスクリプトの関数もしくはメソッドを呼びだしてみましょう。

スクリプトによるインタフェースの実装

スクリプトのメソッドを直接アクセスするには、インタフェースを介して行ないます。つまり、インタフェースの実装がスクリプトということになります。

スクリプトでインタフェースを実装する場合、スクリプトでインタフェースで定義されたメソッドと同一名、同一シグネチャのグローバル関数を定義します。

ここではSwingでボタンをクリックされたときにコールされるリスナをスクリプトで実装してみます。

サンプルのソース InvokeInterfaceMethodSample1.java

ボタンをクリックされた時にはActionListenerインタフェースのactionPerfomedメソッドがコールされるので、スクリプトではactionPerfomred関数を定義します。

    public final static String SCRIPT
        = "function actionPerformed(event) {"
        + "    print('Button Pressed\\n' + event + '\\n\\n');"
        + "}";

このサンプルではクラス中にスクリプトをハードコーディングしてしまっていますが、ファイルから読み込むようにしてもかまいません。

上記のコードではイベントが発生したことを標準出力に出力し、さらにイベントの情報も出力しています。

次にこのスクリプトがインタフェースの実装であることを指定します。

    public InvokeInterfaceMethodSample1() {
        // スクリプトエンジンマネージャの生成
        ScriptEngineManager manager = new ScriptEngineManager();
        // スクリプトエンジンの取得 (JavaScript)
        ScriptEngine engine = manager.getEngineByName("js");
 
        try {
            // スクリプトの評価
            engine.eval(SCRIPT);
            
            // Invocableへのキャスト
            Invocable invocable = (Invocable)engine;
 
            // インタフェースインスタンスの取得
            ActionListener listener 
                = invocable.getInterface(ActionListener.class);

            showFrame(listener);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

スクリプトエンジンを取得し、スクリプトを評価するところまでは今までの例と同じです。

ここで、先週と同様にInvocableインタフェースが登場します。

Invocableインタフェースには評価したスクリプトをインタフェースの実装として返すためのgetInterfaceメソッドが定義されています。getInterfaceメソッドの引数は実装を行なうインタフェースのClassオブジェクトで、戻り値がインタフェースのオブジェクトとなります。

ActionListenerインタフェースの実装なので、赤字で示したようにgetInterfaceメソッドの引数はActionListener.class、戻り値はActionListenerオブジェクトになります。

これでインタフェースの実装としてスクリプトを指定することができました。取得したActionListenerオブジェクトは、ごくごく普通のActionListenerオブジェクトとして扱うことができます。

    private void showFrame(final ActionListener listener) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                JFrame frame = new JFrame("Test");
				
                JButton button = new JButton("OK");
                button.addActionListener(listener);
				
                frame.add(button);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

赤字で示したように、スクリプトで実装したActionListenerオブジェクトを通常のActionListnerオブジェクトと同様にリスナ登録することができます。そして、イベントが発生したらスクリプトのactionPerformed関数がコールされます。

実際に実行して試してみましょう。下の実行結果はボタンを2回クリックした後の標準出力です。この後、何度クリックしても、同じように表示されます。

C:\scripting>java InvokeInterfaceMethodSample1
Button Pressed
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=OK,when=1201277419515,
modifiers=Button1] on javax.swing.JButton[,0,0,115x26,alignmentX=0.0,a
lignmentY=0.5,border=javax.swing.plaf.BorderUIResource$CompoundBorderU
IResource@1fc4bec,flags=296,maximumSize=,minimumSize=,preferredSize=,d
efaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.pla
f.InsetsUIResource[top=2,left=14,bottom=2,right=14],paintBorder=true,p
aintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rollove
rSelectedIcon=,selectedIcon=,text=OK,defaultCapable=true]
 
Button Pressed
java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=OK,when=1201277419515,
modifiers=Button1] on javax.swing.JButton[,0,0,115x26,alignmentX=0.0,a
lignmentY=0.5,border=javax.swing.plaf.BorderUIResource$CompoundBorderU
IResource@1fc4bec,flags=296,maximumSize=,minimumSize=,preferredSize=,d
efaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.pla
f.InsetsUIResource[top=2,left=14,bottom=2,right=14],paintBorder=true,p
aintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rollove
rSelectedIcon=,selectedIcon=,text=OK,defaultCapable=true]

スクリプトのオブジェクトをインタフェースの実装とすることも可能です。

サンプルのソース InvokeInterfaceMethodSample2.java

このとき、スクリプトのオブジェクトは実装するインタフェースで定義されたメソッドと同一名、同一シグネチャのメソッドを定義する必要があります。

    public final static String SCRIPT
        = "var obj = new Object();"
        + "obj.actionPerformed = function(event) {"
        + "    print('Button Pressed\\n' + event + '\\n\\n');"
        + "}";

この場合でも、インタフェースのオブジェクトの取得にはInvocableインタフェースのgetInterfaceメソッドを使用します。ただし、第1引数にスクリプトのオブジェクト、第2引数にインタフェースのClassオブジェクトを指定します。

スクリプトのオブジェクトの取得法は先週紹介したのと同様にScriptEngineメソッドのgetメソッドを使用します。

    public InvokeInterfaceMethodSample2() {
        // スクリプトエンジンマネージャの生成
        ScriptEngineManager manager = new ScriptEngineManager();
        // スクリプトエンジンの取得 (JavaScript)
        ScriptEngine engine = manager.getEngineByName("js");
  
        try {
            // スクリプトの評価
            engine.eval(SCRIPT);
            
            // Invocableへのキャスト
            Invocable invocable = (Invocable)engine;
 
            // インタフェースインスタンスの取得
            Object obj = engine.get("obj");
            ActionListener listener = invocable.getInterface(
                obj, ActionListener.class);
            
            showFrame(listener);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

青字で示した部分がスクリプトのオブジェクトの取得、赤字で示した部分がインタフェースオブジェクトの取得部分です。

スクリプトで実装されたインタフェースオブジェクであっても、通常のインタフェースオブジェクトと同様に扱うことができます。