先週はJavaFX ScriptをJava SE 6から扱いました。JavaFXの処理系にはJSR 223対応のスクリプトエンジンがはじめから含まれており、すぐに使うことが可能です。

今週は別途JSR 223対応のスクリプトエンジンをインストールしなければいけないスクリプト言語処理系の代表としてJRubyを取りあげます。

では、さっそくインストールしましょう。JRubyの本体はJRubyのプロジェクトページからダウンロードできます。原稿執筆時点(2008年2月)での最新バージョンは1.0.3です。したがって、ダウンロードするのはjruby-bin-1.0.3.tar.gzもしくはjruby-bin-1.0.3.zipです。

ダウンロードしたファイルを展開するとjruby-1.0.3というディレクトリーが作成されます。今回は、Windows Vistaを使用し、C:ドライブの直下に展開したとします。

JSR 223対応のスクリプトエンジンはjava.netのScriptingプロジェクトからダウンロードできます。Scriptingプロジェクトはダウンロードページが分かりにくいのですが、左側のサイドバーにある「ドキュメント & ファイル」がダウンロードページへのリンクになります。

ダウンロードするファイルはjsr223-engines.tar.gzもしくはjsr223-engines.zipになります。

これらのファイルにはScriptingプロジェクトで公開しているすべてのスクリプトエンジンがまとめられています。したがって、展開するとそれぞれのスクリプト言語の名前のディレクトリがずらりと並びます。JRubyのスクリプトエンジンの本体はjruby\buildディレクトリにあるjruby-engine.jarファイルになります。

クラスパスの設定を楽にするため、JRubyの本体のJARファイルが配置されているC:\jruby-1.0.3\libディレクトリにコピーしました。

なお、現在公開されているjsr223-engine.zipに含まれているjruby-engine.jarファイルはJRuby 1.0.x用です。現在、RC版が公開されているJRuby 1.1に対応させるにはCVSから最新のソースをダウンロードし、ビルドする必要があります。

インストールができたので、さっそくサンプルを作ってみましょう。

サンプルのソース JRubyEvaluator.java

JRubyのスクリプトの評価は、JavaScriptの場合とほとんど同じです。

    private final static String SCRIPT
        = "nations = %w{Japan US China French World}\n"
        + "nations.each {|nation| puts 'Hello, ' + nation + '!'}";
 
    public JRubyEvaluator() {
        // JRubyスクリプトエンジンの取得
        ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("ruby");
 
        try {
            // スクリプトの評価
            engine.eval(SCRIPT);
        } catch (ScriptException ex) {
            System.err.println("スクリプトの実行に失敗しました");
            System.err.println(ex.getMessage());
        }
    }

JRubyの場合、スクリプトエンジンの名前はrubyもしくはjrubyとします。ここでは、赤字で示したようにrubyにしましたが、どちらでもかまいません。

さて、実行してみましょう。実行にはJRubyやスクリプトエンジンが含まれるJARファイルをクラスパスに設定します。また、スクリプトエンジンにJRubyの本体を示すために、com.sun.script.jruby.loadpath環境変数にjruby.jarを設定します。

ここでは、上述したようにJRubyをC:\jruby-1.0.3に展開し、JRubyEvaluator.classファイルもここに配置したとします。

C:\jruby-1.0.3>java -Dcom.sun.script.jruby.loadpath=lib\
jruby.jar -cp lib\*;. JRubyEvaluator
Hello, Japan!
Hello, US!
Hello, China!
Hello, French!
Hello, World!

JRubyスクリプトエンジンはjavax.script.Invocableインタフェースとjavax.script.Compilableインタフェースの両方を実装しています。したがって、Rhinoスクリプトエンジンとほとんど同じように扱うことができます。

試しにSwingで作成したアプリケーションのリスナをRubyで作成してみましょう。

サンプルのソース Action.rb
JRubySample.java

Swingのアプリケーションなので、Rubyのスクリプト実行もSwingイベントディスパッチスレッドで扱う必要があります。ちょっと長いですが、全体を以下に示します。

    public JRubySample(final String filename) {
        // JRubyスクリプトエンジンの取得
        ScriptEngineManager manager = new ScriptEngineManager();
        final ScriptEngine engine = manager.getEngineByName("ruby");
 
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                try {
                    JFrame frame = new JFrame("Counter");
                    frame.setDefaultCloseOperation(
                        JFrame.EXIT_ON_CLOSE);
                    
                    // カウンターの表示用ラベル
                    JLabel label = new JLabel("0", JLabel.RIGHT);
                    label.setBorder(new EmptyBorder(5, 5, 5, 5));
                    frame.add(label, BorderLayout.CENTER);
 
                    // ラベルをBindingに保持させる
                    engine.put("label", label);
                    
                    // スクリプトの評価
                    Reader reader = new FileReader(filename);
                    engine.eval(reader);
                    
                    // Bindingsからリスナを取得する
                    ActionListener listener 
                        = (ActionListener)engine.get("listener");
                    
                    // 増加用ボタン
                    JButton button = new JButton("Increment");
                    frame.add(button, BorderLayout.SOUTH);
 
                    // スクリプトで作成したリスナを登録
                    button.addActionListener(listener);
                    
                    frame.pack();
                    frame.setVisible(true);
                } catch (FileNotFoundException ex) {
                    System.err.println("スクリプトファイルが存在しません");
                } catch (IOException ex) {
                    System.err.println("入出力エラー");
                } catch (ScriptException ex) {
                    System.err.println("スクリプトの実行に失敗しました");
                    System.err.println(ex.getMessage());
                }
            }
        });
    }

フレームにはラベルとボタンが配置されています。ラベルには数字が表示されており、ボタンをクリックしたときのイベント処理をスクリプトで行ないます。

スクリプトではラベルの表示を更新しているため、Bindingsオブジェクトにラベルを保持させます(青字部分)。また、リスナはスクリプトのグローバル変数として定義してあるので、それをBindingsオブジェクトから取得します(赤字部分)。

次にスクリプトです。

include Java
 
class CounterListener 
  include java.awt.event.ActionListener
 
  def initialize
    @count = 0
    @label = $label
  end
 
  def actionPerformed(event)
     @count += 1
     @label.text = @count.to_s
  end
end
 
$listener = CounterListener.new

JRubyでJavaの機能を使用するには include Java を実行します。インタフェースを実装するにはクラス定義の部分でインタフェースを include します。

このスクリプトではBindingsインタフェースで扱うため、2つのグローバル変数$labelと$listenerがあります。$labelはインスタンス変数に代入し、イベントが発生したらラベルの文字列を変更するようにしました。

@label.textはJRubyでJRubyのsetTextメソッドに置き換えられます。

最後にこのスクリプトで定義したCounterLsitenerクラスのオブジェクトを生成し、$listenerに代入します。

これで、Javaからこのリスナを扱えるようになります。

コードが完成したので、さっそく実行してみましょう。図1に実行結果を示します。a)が初期状態、b)が5回ボタンをクリックしたところです。

今週はJRubyを取りあげて、Javaと一緒に使う方法を解説しました。JRuby自体の説明はほとんどしませんでしたが、興味のある方はぜひJRubyのWebサイト、もしくはwikiを覗いてみてください。

今週まで9回に渡って、Java SE 6のスクリプト対応の解説を行なってきました。来週はスクリプトをどのように活用するかを考えていく予定です。お楽しみに。

初期状態 5回クリック後
a) 初期状態 b) 5回クリック後
図1 JRubySampleの実行結果

 

著者紹介 櫻庭祐一

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

今月の櫻庭

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

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

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