読者です 読者をやめる 読者になる 読者になる

たごもりすメモ

コードとかその他の話とか。

Javaの Class.forName() メソッドを経由してJRubyで定義したclassを取得する方法は無いものか(追記あり)

JRuby経由でEsperを使ってるんだけど、そこにこんなコードがある。

String className = desc.getFunctionClassName();
Class clazz;
try 
{   
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    clazz = Class.forName(className, true, cl);
}
catch (ClassNotFoundException ex)
{   
    throw new EngineImportException("Could not load aggregation class by name '" + className + "'", ex);
}

このコードで解決される clazz にどうにかしてJRubyで定義したclassを渡したいなーと思うんだけど、うまくいかない。どうすればいいんだ、という話。JRuby 1.7.4。

JRubyで定義するクラスのパッケージ名

JRuby上で定義したRuby classに java のパッケージ名をつけるには java_package というものを使う、らしい。

java_package 'is.tagomor.test'
class Hoge
  # ...
end

こうすると fully qualified class name としては is.tagomor.test.Hoge となる……らしいんだけど、これが本当に指定されてるかどうかよくわからないんだよな。

次のようなコードを実行してみると ClassNotFoundException になる。

require 'java'

java_package 'is.tagomor.test'
class Hoge; end

loader = JRuby.runtime.jruby_class_loader
java.lang.Class.forName('is.tagomor.test.Hoge', true, loader)

class定義部分だけ jar にしといたらいいのかなーと思ったけど、してみてもダメ。

$ cat hoge/hoge.rb 
require 'java'

java_package 'is.tagomor.test'
class Hoge; end

$ jrubyc hoge/hoge.rb 
$ jruby -r hoge.jar x.rb
$ LC_ALL=C jar -cfe hoge.jar hoge/hoge.class hoge
$ cat x.rb
require 'java'
loader = JRuby.runtime.jruby_class_loader
java.lang.Class.forName('is.tagomor.test.Hoge', true, loader)

$ jruby -r hoge.jar x.rb 
URLClassLoader.java:202:in `run': java.lang.ClassNotFoundException: is.tagomor.test.Hoge
	from AccessController.java:-2:in `doPrivileged'
	from URLClassLoader.java:190:in `findClass'
	from JRubyClassLoader.java:92:in `findClass'
	from ClassLoader.java:306:in `loadClass'
	from ClassLoader.java:247:in `loadClass'
	from Class.java:-2:in `forName0'
	from Class.java:249:in `forName'
 (以下省略)

どうすればいいんだ。どなたかご存知ありませんか。

追記 at 8/8

親切な人にいろいろ教えてもらいました!

パスをがんばる

いったんjarに落とせる場合にはコメント欄に id:kimutansk さんにもらった方法でいいっぽい。つまりJavaっぽくパッケージ名に沿ったかたちでディレクトリ階層を掘り、そこに置いた .rb のファイルを .class にコンパイルして jar にまとめ、それを読み込む。

JRubyJavaコードへのコンバータとして使う感じ。実行でるけどちょっと面倒ですな。

become_java! を引数つきで呼ぶ

require 'jruby/core_ext' した後で使える Class#become_java! メソッドJavaクラスを作ってくれるとのことで試してみたけど、どうにもうまくいかない。
で、JVMが読み込める位置に .class ファイルをダンプしてやる必要があるとかなんとかで、つまり become_java! を引数つきで呼ばないといけないようだ。というか、そうするようにしたらうまくいった。 @yasushia さんに教えてもらいました。

require 'java'
require 'jruby/core_ext'
class Hoge; end

klass = Hoge.become_java!(".")
cl= java.lang.Thread.current_thread.getContextClassLoader
p java.lang.Class.forName(klass.get_name, true, cl)

このあたりを見るとパスじゃなくて適切なクラスローダのインスタンスを与えてもいいっぽいけど、Esperの中から該当のインスタンスをひっぱり出してくる方法がアレなので今のところ未確認。

https://github.com/jruby/jruby/blob/master/lib/ruby/shared/jruby/core_ext/class.rb#L50..L60

これでRubyっぽいコードのままJavaの世界に実装をもっていける! やったやった! ありがとうございました!