: O. Yuanying

JRubyのクラスをJavaで実装する

JRubyから利用するJavaで実装したクラスのオブジェクトのメソッドからブロックを利用したいと思い立つ。

そもそもJavaで実装したクラスはJRubyからinclude_class 'java.util.Random'とかすれば簡単に利用することができるのだけれども、そのメソッドはブロックを受け取ることができないし、JRubyで定義したクラスのオブジェクトを柔軟に利用することができない。

少しソースを調べたところ、IRubyObjectを実装すれば良さそうな気がしてきたので、とりあえずHello Worldを試してみた。

目標はHelloWorldというクラスをJavaで実装し、以下のように利用できるようにすること。

require 'helloworld'

hw = HelloWorld.new('Yuanying')
hw.helloworld()     # -> 'Hello World!'
hw.hello { |name|
    puts 'Hello! ' + name   # -> 'Hello! Yuanying'
}

まずはブロックを受け取るメソッドを実装するのは難しそうなので、helloworld()のみ実装してみる。

package jp.fraction.test;

import org.jruby.IRuby;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;

public class RubyHelloWorld extends RubyObject {
	
	public RubyHelloWorld(IRuby runtime, RubyClass metaClass) {
		super(runtime, metaClass);
	}

	public IRubyObject initialize() {
		System.out.println("initialize");
		return this;
	}

	public IRubyObject helloworld() {
		System.out.println("Hello World!!");
		return this;
	}

    /**
     * HelloWorldクラスをRubyのランタイムに登録する関数。
     */
	public static void registerHelloWorldClass(IRuby runtime) {
	    // HelloWorldクラスを登録する。
	    // クラス名、スーパークラス、インスタンス生成用のアロケータ
		RubyClass rubyClass = runtime.defineClass("HelloWorld", runtime
				.getObject(), RubyHelloWorld.HELLOWORLD_ALLOCATOR);
		
		CallbackFactory callbackFactory = runtime
				.callbackFactory(RubyHelloWorld.class);
		
		// initializeメソッドの登録。
		rubyClass.defineMethod("initialize",
				callbackFactory.getFastMethod("initialize")); 
		
		// helloworldメソッドの登録。
		rubyClass.defineFastMethod("helloworld",
				callbackFactory.getFastMethod("helloworld"));
	}
	
	public static ObjectAllocator HELLOWORLD_ALLOCATOR = new ObjectAllocator() {
		public IRubyObject allocate(IRuby runtime, RubyClass klass) {
			return new RubyHelloWorld(runtime, klass);
		}
	};
}

これで、RubyHelloWorld.registerHelloWorldClass()を現在実行中のランタイムを引数にして実行すれば、HelloWorldクラスがランタイムに登録される。

さて、クラスがロードされるのはrequire 'helloworld'した時のみにしたい。

ちょっと調べてみるとLoadService#registerBuiltin()というメソッドを発見する。requireされたときに、LoadServiceからスクリプトがロードされるのだが、ビルトインとしてLoadServiceに登録しておけばrequireされた時にロードしてくれる模様。

と言うわけでこんな感じ。

IRuby runtime = ...
runtime.getLoadService().registerBuiltin("helloworld.rb", new Library() {
    public void load(IRuby runtime) throws IOException {
	   RubyHelloWorld.registerHelloWorldClass(runtime);
	}});

さて、問題は現在実行中のランタイムをどーやって拾ってくるかということだが…。