JSR 223 JRuby script engineを使うサンプルコード

JRubyを使うと、JavaのインタフェースをRubyで実装できるようになります。これをJDK1.6で導入されたJSR 223 Scripting APIを使うと、こんなふうにプログラミングできます。

まずは、Javaのインターフェースを定義します。

package lycoris;

public interface Greetings {
    public void hello();
    public void bye(String to);
}

これをJRubyで実装するには、こんなふうにします。

#greetings.rb
require 'java'

class GreetingsImple
  import 'lycoris.Greetings'
  
  def hello
    puts "What's up?"
  end

  def bye(to)
    print "See you, ", to, "\n"
  end
end

greetings = GreetingsImple.new
greetings.hello
greetings.bye("Scarecrow")
greetings

import文がJavaでいうimplementsにあたります。最後の行でインスタンスgreetingsを返していますが、これがないとJRuby engine側でGreetingsインタフェースを取得できないので忘れないようにします。
Scripting APIを使ってGreetingsインタフェースのメソッドを実行するにはこんなプログラムを書きます。

package lycoris;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class InvocableSample {
    public static void main(String argv) {
        try {
            //JRuby用エンジンを取得
            ScriptEngineManager manager = new ScriptEngineManager();
            ScriptEngine engine = manager.getEngineByName("jruby");

            //JRubyのスクリプトを読み込んで一度evalしておきます。
            //Invocableインタフェースを使うにはあらかじめevalされていることが前提です。
            String testname = "/home/yoko/NetBeansProjects/Learning/lib/greetings.rb";
            //ScriptEnfine.FILENAMEでファイル名を指定するとスクリプトがファイルとして扱われます。
            //指定しなくても問題ありませんが、大きいスクリプトのときは指定するといいです。
            engine.put("ScriptEngine.FILENAME", testname);
            //スクリプトからインスタンスが返ってくる
            Object obj = engine.eval(new FileReader(testname));

            //Invocableインタフェースを取得
            Invocable invocable  = (Invocable)engine;
            //Greetingsインタフェースで定義した二つのメソッドを名前を指定して実行します。
            invocable.invokeMethod(obj, "hello", new Object{});
            invocable.invokeMethod(obj, "bye", new Object[]{"Santa Claus"});
            //evalしたときに返されたインスタンスからGreetingsインタフェースを取得します。
            Greetings greetings = invocable.getInterface(obj, Greetings.class);
            //Greetingsインタフェースで定義した二つのメソッドを実行します。
            greetings.hello();
            greetings.bye("Witch");
        } catch (NoSuchMethodException ex) {
            Logger.getLogger(InvocableSample.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ScriptException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
        }
    }
}

このプログラムを実行すると

What's up?
See you, Scarecrow
What's up?
See you, Santa Claus
What's up?
See you, Witch

のように出力されます。一つめのメッセージの組はスクリプトをevalしたときに出力されたものです。
このサンプルで使っているのはjruby 1.0.2のjruby.jarとhttps://scripting.dev.java.net/CVSから取得できるバージョンのJRuby engineです。このサンプルでは日本語を使っていませんが、日本語のメッセージはputsする限りは大丈夫だと思います。printは文字化けすることがありますが、これはJRuby側の問題で、jruby 1.1b1なら文字化けしません。ただし、JRuby engineは1.0.xで動くバージョンしかCVSに置いていないので。。。(手元にはjruby 1.1版JRuby engineがあるので、近いうちになんとかしたいと思います。)
JavaのインタフェースをRubyで実装する例として

#runnable.rb
require 'java'

class RunnableImple
  import java.lang.Runnable
  
  def run
    puts "Hi, everyone."
  end
end

java.lang.Thread.new(RunnableImple.new).start
RunnableImple.new

のようなものをあちらこちらで見かけます。これをScripting API

String testname = "/home/yoko/NetBeansProjects/Learning/lib/runnable.rb";
engine.put("ScriptEngine.FILENAME", testname);
Object obj = engine.eval(new FileReader(testname));
Runnable runnable = invocable.getInterface(obj, Runnable.class);
runnable.run();

のようにしてrunメソッドを実行できるのですが、これもjruby 1.1b1じゃないと動きませんでした。そもそも、runnable.rbが1.0.2じゃ動きません。