Rubyスクリプト実行の流れ
Java内からRubyスクリプトを呼び出す処理について簡単にまとめておきましょう。まず、Rubyのランタイム環境を管理するRubyクラスのインスタンスを用意しておきます。これは以下のようにして取得します。
Ruby ruby = Ruby.getDefaultInstance();
Rubyの実行に関する機能は、このRubyインスタンスの中に用意されています。続いて、Rubyのスクリプトファイルからスクリプトを読み込むためのFileReaderを用意します。
FileReader reader = new FileReader("test.rb");
これで準備は完了です。RubyインスタンスのevalScriptを呼び出し、FileReaderを渡せば、そこからスクリプトを読み込んで実行できます。
IRubyObject obj = ruby.evalScript(reader, "");
実行後の結果は、IRubyObjectというオブジェクトとして返されます。ここでは、スクリプト側でただのStringが返されるようになっていましたので、そのままSystem.out.printlnしています。
また、ここではevalScriptにFileReaderを渡していますが、実をいえば引数にStringを渡すこともできます。つまり、ファイルからスクリプトを読み込むのでなく、実行するテキストをそのまま渡して実行させることもできるのです。これはなかなか便利ですので覚えておきたいものです。
Rubyクラスを利用する
Rubyもオブジェクト指向言語ですから、クラスを定義し利用します。では、単純にスクリプトを実行するだけでなく、Rubyのスクリプトに定義されたクラスをJava内から利用することはできるのでしょうか。
これも実際にやってみましょう。先ほどの「test.rb」のスクリプトを、クラスの形に修正してみます。
require 'java' class MyDateTime include_class 'java.util.GregorianCalendar' def getDateTime cal = GregorianCalendar.new date = cal.getTime date.toString end end
続いて、「RuybSample.java」のソースコードを修正し、「test.rb」を読み込んだ後、MyDateTimeクラスのインスタンスを作成してgetDateTimeメソッドを呼び出してみましょう。
package jp.codezine; import java.io.*; import org.jruby.*; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; public class RubySample { public static void main(String[] args) { Ruby ruby = Ruby.getDefaultInstance(); ThreadContext context = ruby.getCurrentContext(); try { FileReader reader = new FileReader("test.rb"); ruby.evalScript(reader, ""); RubyClass rclass = ruby.getClass("MyDateTime"); IRubyObject robj = rclass.callMethod(context,"new"); IRubyObject obj = robj.callMethod(context,"getDateTime"); System.out.println("Result: " + obj); } catch (FileNotFoundException e) { e.printStackTrace(); } } }
ここではevalScriptでスクリプトを読み込み実行した後、スクリプト内のクラスを利用しています。まず、RubyインスタンスのgetClassを使い、RubyClassを取得します。
RubyClass rclass = ruby.getClass("MyDateTime");
このRubyClassが、文字通りRubyのクラスを示すものとなります。続いて、このクラスからインスタンスを作成します。
IRubyObject robj = rclass.callMethod(context,"new");
callMethodは、RubyClassのメソッドを呼び出すためのものです。これは、ThreadContextというクラスのインスタンスと、呼び出すメソッド名のStringを引数にして渡します。ThreadContextは、mainの最初の方でRubyインスタンスのgetCurrentContextを呼び出して取得してあるのが分かるかと思います。
IRubyObject obj = robj.callMethod(context,"getDateTime");
こうしてインスタンスが作成できたら、callMethodでgetDateTimeメソッドを呼び出します。返値は、evalScriptと同様にIRubyObjectインスタンスとして得られます。
引数を持つメソッドの呼び出し
このgetDateTimeは引数を持たないメソッドでしたので、単にメソッド名を指定するだけで呼び出すことができました。では、引数を持ったメソッドを呼び出す場合はどのようにすればよいのでしょうか。先ほどのMyDateTimeクラスを修正してみましょう。
require 'java' class MyDateTime include_class 'java.util.GregorianCalendar' include_class 'java.text.SimpleDateFormat' def getDateTime str1 cal = GregorianCalendar.new fm = SimpleDateFormat.new fm.applyPattern str1 date = cal.getTime fm.format date end end
getDateTimeに日付フォーマットのためのパターンを引数にして渡すと、それを使ってSimpleDateFormatでフォーマットされた日付表記のStringを返すようにしました。では、RubySampleクラスのmainメソッドを修正して、この修正getDateTimeを呼び出すようにしてみましょう。
public static void main(String[] args) { Ruby ruby = Ruby.getDefaultInstance(); ThreadContext context = ruby.getCurrentContext(); try { FileReader reader = new FileReader("test.rb"); ruby.evalScript(reader, ""); RubyClass rclass = ruby.getClass("MyDateTime"); IRubyObject robj = rclass.callMethod(context,"new"); IRubyObject[] objarr = new IRubyObject[]{ruby.newString("yyyy/MM/dd")}; IRubyObject obj = robj.callMethod(context,"getDateTime",objarr); System.out.println("Result: " + obj); } catch (FileNotFoundException e) { e.printStackTrace(); } }
引数付きメソッドの場合には、引数をIRubyObject配列として用意し、これをcallMethodに渡します。このIRubyObjectはインターフェースですので、これを実装したクラスのインスタンスであれば設定できます。ここでは、RubyクラスのnewStringというものをつかい、RubyStringインスタンスを作成して設定しています。テキストの値を渡す場合は、大抵これでOKです。Rubyクラスには、Rubyで扱われるさまざまな値を生成するメソッドが用意されているので、これらを使ってオブジェクトを生成して配列に収めていけばいいでしょう。
こうして作成されたIRubyObject配列が、呼び出したメソッドに引数として渡され、配列のそれぞれのオブジェクトの値がメソッドで利用できるようになる、というわけです。引数がない場合に比べるとやや煩雑ですが、配列の準備方法さえ理解できればそう難しいものではありません。
まとめ
今回は、JRubyのインストール、Eclipse 3.3での利用、RubyからJavaのクラスライブラリを利用する方法、JavaからRubyのスクリプトやクラスを利用する方法まで、一通りをまとめて説明しました。JRubyのさわり程度ですが、これがどういう使い方ができるのか、イメージぐらいはつかめたのではないでしょうか。
JRubyは、Java環境上で動くスクリプト言語の中でも非常に重要なものの1つでしょう。RubyはJavaと同じオブジェクト指向言語とはいえ、かなりJavaとは違った形をしています。Javaは、作成する処理が決まれば、誰が書いても大体できあがるソースコードは同じようなものになりますが、Rubyはさまざまな表現が可能です。Javaプログラマにとっては、Rubyに触れることは非常に大きな刺激となるはずです。
