Hatena::ブログ(Diary)

技術日記@kiwanami

2011-02-20

Linux上のJavaアプリでいわゆる「IMEをON」にする方法

結論から書きますと、今回紹介する方法で強引にONにはできますが、Javaプログラム上で一般的に行う方法は無いようです。

きっかけ

Javaで日本語入力を伴う業務アプリを作ると、かならずIMの制御が出てきます。Windowsだと以下のような方法で「たまたま」可能です。(Vista以降は不明)

なぜ「たまたま」なのかは以下の文章やドキュメントが詳しいです。

Java IMFでは「IMを On/Offする」という概念はありません。

(中略)

言語を日本語に切り替えれば日本語IMがOnになるし、中国語に切り替えれば中国語IMがOnになるという考え方です。

(中略)

On/Off の概念のないJava IMF の仕組みの下で、Windowsの元々持っているOn/Off の仕組みが動いてしまっていると言うことです。

一方のMacは微妙らしいです。

そして、肝心のLinux(少なくともIIIMのATOK)では、上の方法は期待した結果になりません。しかももっと悪いことに、入力フィールドを移るごとに強制的にIMがOFFになります。WindowsだとウインドウごとにIMの状態が維持されていたような気がします。

どんなときに困るかというと、入力フィールドがたくさん(何十個とかそれ以上)あるときに、それぞれでIMのONをする必要があり、それが死ぬるほどめんどくさいのです。具体的には FreeMind 上で項目を入力するごとにIMをONにしなければならず、この動きが毎回とてつもなく思考を妨げるため、わざわざVMWareWindows を立ち上げて FreeMind を使うほどでした。

調査と失敗

ということで、Windowsの実装がたまたまそうなっているのなら、Linuxの実装もそうできるかも知れないと思い、JTextFieldから掘って調べてみました。

ソースコードからネイティブ一歩手前のコードまで探して、以下のようなコードでprivate変数まで調べてみましたが、捜し物はみつかりませんでした。

private void reportIC() {
    System.out.println("IC:"+textfield.getInputContext());
    if (textfield.getInputContext() != null) {
        sun.awt.im.InputContext icc = (sun.awt.im.InputContext)textfield.getInputContext();
        try {
            System.out.println("-------");
            Method met = icc.getClass().getSuperclass().getDeclaredMethod("getInputMethod",null);
            met.setAccessible(true);
            sun.awt.X11.XInputMethod im = (sun.awt.X11.XInputMethod)met.invoke(icc,null);
            String[] fields = {"clientComponentWindow",
            "awtFocussedComponent",
            "lastXICFocussedComponent",
            "isLastXICActive",
            "isActive",
            "isActiveClient",
            "highlightStyles",
            "disposed",
            "needResetXIC",
            "needResetXICClient",
            "compositionEnableSupported",
            "savedCompositionState",
            "committedText",
            "composedText",
            "rawFeedbacks",
            "pData",};
            for(int i=0;i<fields.length;i++) {
            	try {
                	java.lang.reflect.Field fs = X11InputMethod.class.getDeclaredField(fields[i]);
                	fs.setAccessible(true);
                	System.out.println("IC:"+fs.getName()+" / "+fs.get(im));
            	} catch (Exception ee) {
            		System.out.println("IC:"+fields[i]+" / "+ee.getMessage());
            	}
            }
            String[] fields2 = {"xicFocus"};
            for(int i=0;i<fields2.length;i++) {
            	try {
                	java.lang.reflect.Field fs = XInputMethod.class.getDeclaredField(fields2[i]);
                	fs.setAccessible(true);
                	System.out.println("IC:"+fs.getName()+" / "+fs.get(im));
            	} catch (Exception ee) {
            		System.out.println("IC:"+fields[i]+" / "+ee.getMessage());
            	}
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

ちなみに今回のようなGUIのイベント系の調査の場合、 Eclipseデバッガでは フォーカスが移るなどのイベントが大量に発生するのでクラスの状態を観察しづらいです。こういうときは各地にログを入れてタイミングや順番を観察したり、直接private変数にアクセスする方法が有効です。

イベントの伝搬を調査してみるも、IMのON/OFFはJavaには流れてきてないようです。dispatchEventの実装を見る限り、もっと低レベルで処理されているような感じです。そもそもそういうアーキテクチャなので無理もないのかもしれません。

Robotで実現

やけになってRobotを使ってIMをONにするキーをエミュレートすると、とりあえずONには出来ることが分かりました。

ただ、やっぱり現在のIMの状態が取れないので、前の入力フィールドのIMの状態を引き継ぐには何か工夫する必要があります。入力された文字列の最後の文字が非ASCIIだったらONという条件で試してみたら、大抵の場合においてそれらしく動くので、自分の場合はこれでいいことにしました。以下のようなコードです。

private static boolean kanjiFlag = false;

public static void kanjiStart() {
	System.out.println("START : -> "+kanjiFlag);
	if (kanjiFlag) {
		kanjiStartGen();
	}
}

public static void kanjiEnd(String s) {
	if (s != null && s.length()>0) {
		if (s.charAt(s.length()-1) > 0x127) {
			kanjiFlag = true;
		} else {
			kanjiFlag = false;
		}
	}
	System.out.println("END : "+s+"  -> "+kanjiFlag);
}

private static void kanjiStartGen() {
	Thread thread = new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				Robot rb = new Robot();
				rb.setAutoDelay(50);
				rb.keyPress(KeyEvent.VK_CONTROL);
				rb.keyPress(KeyEvent.VK_SPACE);
				rb.keyRelease(KeyEvent.VK_SPACE);
				rb.keyRelease(KeyEvent.VK_CONTROL);
			}catch (AWTException e) {
				e.printStackTrace();
			}
		}
	});
	thread.start();
}

入力フィールドの focusGeined で上の kanjiStart() を呼び、focusLost もしくは内容確定時に kanjiEnd(textfield.getText()) を呼ぶ感じです。

完璧ではありませんが、無いより遙かにマシになりました。

2010-12-15

大量のPDFファイルを右綴じに変えたい

最近、スキャン対象が縦書きの本に突入してきた。右綴じにする方法がすごく大変。いちいちAcrobatプロパティで変えるのはちょっと無理。

そこで、「pdf 綴じ方 変更」や「pdf 右綴じ scansnap」で探してみたが、定番のツールがあまりない。いくつかフリーソフトシェアウエアが見つかったがどれも怪しい。

結合にはUbuntu上で pdftk を使ったが、これにも右綴じにする方法はない。

いやいや、実はそんな大したことではないはずだろうと思ってPDF仕様書を見てみる。

CatalogのViewerPreferencesにDirectionでR2Lを指定すればいいらしい。

いろいろ試した結果、iTextを使う方が楽そうだったのでJavaで。R2Lにするのは実質3行。

import java.io.File;
import java.io.FileOutputStream;
import com.itextpdf.text.pdf.*;

public class MainApp {

    public static final void main(final String[] args) {
        if (args == null || args.length == 0) {
            System.out.println("Usage > java -jar pdfr2l.jar (files)...");
        } else {
            for (String i : args) {
                try {
                    System.out.print("Prcessing : "+i);
                    doIt(i);
                    System.out.println(" OK");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static final String tmpFilename = "_tmp.pdf";

    private static void doIt(String file) throws Exception { 
        File orgFile = new File(file);
        File tmpFile = new File(tmpFilename);
        //右綴じのコード
        PdfStamper st = new PdfStamper(new PdfReader(file),new FileOutputStream(tmpFile));
        st.addViewerPreference(PdfName.DIRECTION, PdfName.R2L);
        st.close();
        //ここまで
        
        if (!tmpFile.exists()) {
            throw new RuntimeException("Can not generate a PDF : "+file);
        }
        if (!orgFile.delete()) {
            throw new RuntimeException("Can not delete the original PDF file : "+file);
        }
        if (!tmpFile.renameTo(orgFile)) {
            throw new RuntimeException("Can not rename the generated PDF file : "+file+" <- "+tmpFilename);
        }
    }
}

iTextjarと一緒にコンパイルしてjarにして使ってる。pdftkみたいにgcjバイナリにするともっと使いやすそう。

既存のPDFに何かちょっとした変更を加えるのは PdfStamper を使うよう。Stampだけでなくて、ページを追加したり結構いろいろ出来る。

PDFやるのならiTextで決まりなんだけど、歴史が長い分ちょっとドキュメントが古いところがある。

2007-08-02

kiwanami2007-08-02

JRubyの中の人

急遽、 Thomas Enebo さんと語る会。ヌーラボ社にて午前10時より1時間ちょっと。人数は10人ぐらい。

yajbを作っていて気になったところなどを聞いてみた。英語をしゃべるのが難しかったが、非常に楽しかった。もっといろいろ聞いてみたかったけども、プロジェクトは立て込んでいるし、平日なのでなかなか大変。

JRubyプレゼン

  • Rubyの現状
  • JRuby紹介
  • JRubyデモ:irbで補完やSwing
  • RailsとJRuby
  • NetBeans紹介

質問コーナー

※kiwanami主観入り。

Q:JRubyのパフォーマンス改善はどうやっているか?

A:いろいろ。

マイクロベンチマークの繰り返しで重いところを改善する。結局、標準添付ライブラリ(IOとかnetとか)が遅かったりするのでなかなか難しい。

JVMバイトコードへのコンパイルを進めている。大体2倍くらい速くなるらしい。今は一部だが将来的には任意のRubyコードを変換できるようにする予定。YARVのバイトコードをJVMバイトコードに単純変換して動かすものも考えている。

Q:オーバーライドメソッドの解決方法は?

A:経験的方法。引数の型を見て適当に判断。

厳密にやろうとすると大変。同じ名前のメソッドならばDuckTypingに期待して、ある程度互換な型であれば動くことを期待してもいいのではないか。基本的に100%の変換はありえない。

Q:数値型の相互変換は大変そうだがどうしているか?

A:Rubyの型に変換して計算。

JavaとRubyとの相互変換の際に曖昧性やパフォーマンス低下が起きている。ただ、今後Javaの数値を変換するのではなく、DuckTypingの要領でJavaの数値オブジェクトをラップしてそのまま変換せずに計算してしまうようなMathエンジンを考えている。

Q:Bytecodeツールは?

A:ASM。

Javassistは知らなかったみたい。

Q:Javaのクラスの継承はできる?

A:出来る。

ただし、クラス継承機能自体にいくつかバグがある。また、protectedメソッドへのアクセスが出来ないなどの問題がある。

Q:Rubyの環境は何を?

A:NetBeansとちょこっとEmacs。

NetBeansのRailsデバッグが非常に強力。JRubyなのでデバッグも出来るし。正規表現の補完、初出のローカル変数の特定もできる。

がたがた言わずにこれからはNetBeansで決まりじゃね?

※8/4写真追加。

2007-05-02

今日のまとめ

自分がどこから来たのか見つけられない土地の人(id:nowokay)と飲んだ。

  • 最近はビールやお酒とコーラやジンジャーエールとの違いが分からなくなった。
  • デパートでは荷物をまとめてくれようとして、社名入りの紙袋がよく支給される。
  • 一人分の調理は難しい。特ににんじんは使うのが難しい。
  • カレーにジャガイモなど入れない。
  • 昔しか知らないが、 Microsoft のターゲットに多分自分は入っていなかったらしく、辛い思い出しかない。
  • Javaはターゲットがないだけに、自分でいろいろ出来る自由があって楽しい
  • JavaScript+継続フレームワークはevalすると実行中に動きが変えられる上に、this.toSource() すると、書き換えた結果が取れてさらにうれしい。
  • トランザクションも画面遷移に関係なく範囲指定できるので楽しい。
  • 東京では、自分で頼んでうまいものを食べれた記憶があまりない。
  • 川田最高。
  • Javaの言語仕様より、JVMの発展が重要。
  • Javaはライブラリの粒度が小さいか、大きすぎ。必ず組み合わせないといけなくて、そこがJavaが完結していないとか、難しいと言われるところではないか。
  • GWも仕事ですが何か。

2007-04-29

yajbについていろいろ

そろそろyajbのバージョンアップを考えている。内部的にはプログラムは出来ていて、後はリリースノート書いたりという作業などがあって、なかなか進まないでいる。せっかくのRubyForgeなのだけども共同作業などもはじめる余裕がなくて難しい。

とりあえず、yajbについてのメモや記録などをいったん全部出してみて、自分的なプレッシャーをかけてみる。

yajbの設計と実装


以下、開発のメモを手直し。

流れなど

はじまりはこのあたり。

RubyでJava

で、実装出来そうだという手ごたえは、Jlambdaを作ったときの感触から。

Jlambda

その後、1ヶ月ほどで普通に使えるようになって、1ヶ月ぐらいがんばって英語でドキュメントを書いて公開。ぽつぽつと直しながら、ここ1年ぐらいは放置気味。一応、毎月メールでの質問が来るのでそれに答えるくらい。

最近急速にJRubyが良くなってきているので、そろそろ役目は終わりかなとか思っている。

Java-RubyをまたぐGC

Ruby側のGCのタイミングをObjectSpaceで拾うことができるので、そこからJava側にGC対象のオブジェクトを知らせることで何とか実現。単にJavaをラップしている代理オブジェクトはこれでいけた。

ただし、Javaから呼ばれるRubyオブジェクトについては、JavaとRubyの両方から要らないと判断されない限り捨てられない。いろいろ考えた結果、これらのオブジェクトは手動で削除することにした。

例えば、java.awt.event.ActionListener を作ってボタンに割り当てた場合、ActionListenerはJavaのオブジェクト同士で結びついているので、Ruby側ではその結びつきを直接知ることができない。なので、Ruby側ではそのブロックが終了するとActionListenerのオブジェクトはGCの対象になる。しかし、ここでGCしてしまうとボタンのイベントが発生したときにRuby側で対応するオブジェクトが居なくなる。

ではと、Java側でSoftReferenceで保持しておいて、Java側でGCされたタイミングでRubyもGCするということを考えてみる。そうすれば、Java側のオブジェクト同士のリファレンスの切断をRubyで検出できる。しかし今度は、ActionListenerを作ってボタンに登録する間にGCされる可能性ことがあることが分かった。

確実なのは、JavaからもRubyからも必要ないオブジェクトはGCしてもいいということ。しかし Pure Ruby、Pure Javaで実装すると、必要ないという判断はGCした後でしか分からないため、要するに自動でGCすることは出来ない事がわかった。

HORBとかCORBAとか、既存の分散系の実装を見ておけばもう少し楽に設計できたかもしれない。

オブジェクトの同一性

Ruby側のオブジェクトで比較せずに、Java側のオブジェクトで同一判定するようにした。

今まで、同一のJavaオブジェクトには同一の代理オブジェクトを返すようにしていたのだけども、それをやめて同一のJavaオブジェクトに対してRuby側の代理オブジェクトを必要に応じて複数生成するようにした。

これにより代理オブジェクトの再利用などを考えなくて良くなったのでGCが実装しやすくなったけども、リファレンスカウンタを制御する必要が出てきて、オブジェクト生成系とGC周りがかなり大きくなった。

Javaのクラス・インタフェースをRubyで実装

javassist でインタフェースやクラスを implements, extends したクラスを新規作成して、それらが持っている finalやprivate 以外のメソッドをオーバーライドしてしまうという、非常に強引で普通な方法を用いた。

通常は java.lang.relrect.Proxy クラスを使うのだけども、そうするとインタフェースしか実装できず、任意のクラス(たとえばWindowAdapterとか)を継承できなくて実際には使いにくい。ここはSwingがまともに使えるという要件から外せないポイントとしてがんばってみた。

オーバーライドの中身は、デフォルトでは親クラスの実装を呼ぶか、Rubyへの呼び出しになっている。Rubyの Module#define_method を使ってRuby側で実装した旨をJava側に通知することで、Javaのクラスの動的な継承が出来るようにした。

かなりjavaコードを動的に生成するため、独自のテンプレートエンジンを作った。Velocityなどを使うことも検討したのだけども、Velocityだとオーバースペック過ぎるのと、依存ライブラリを増やしたくないということで自作。なかなかテンプレートコードやテンプレートエンジンはコンパクトに実装できて良かったと思う。

困ったことは、Javassistはコンパイルのエラーメッセージがまともに出てこないため、デバッグに非常に苦労したこと。でも、コツをつかめば大丈夫。

これでJavaのコードを1行も書かずにRubyからJavaを制御できるようになった。

マルチスレッド

問題はSwingを使うとデッドロックするというもの。具体的には、JavaからRubyを呼び出すと、呼び出したスレッドはその呼び出しの結果が返ってくるまでブロックされるため、GUIのイベントハンドラでRubyを呼び出してそこでGUIを更新したりすると、Swingの1スレッドルールによってデッドロックが発生する。

解決法のアイデアは早くから思いついていて、JavaからRubyを呼び出したときに単にスレッドをブロックさせるのではなくて、スレッドを再利用可能な形で休眠させるというもの。休眠中にRubyからJavaの呼び出しが発生した場合は、そのスレッドに仕事をさせることでデッドロックを回避する。いわゆる、スレッドを使った継続の実装。

実装は以下のような感じ。

  • Java→Rubyの呼び出す部分を集中管理
  • Java→Rubyの呼び出しにセッションIDを割り振る
  • Java→Rubyの呼び出しがあった場合、スレッドは以下の要求のために待機する
    • Rubyの呼び出しが終了
    • RubyからJavaの呼び出しが発生
  • Ruby→Javaの呼び出しがあった場合、セッションIDに対応する休眠中のスレッドに仕事をさせる
    • 仕事が終わったら休眠に戻る
  • Rubyの呼び出しが終わったら、休眠を解除してセッションを終了

これでとりあえずSwingのデッドロックがなくなった。また、 JComponent#paintComponent(Graphics) を実装して絵もかけるようになった。その他、Javaのスレッドやロックに起因していた問題が全て解消した。すばらしい。

Swingが普通に動くようになったことで、RubyのクロスプラットフォームGUIツールキットとしての野望に一歩近づいた。

パフォーマンス

明らかに通信が遅いので、通信部分をまるごと取り替えるようにした。もともとJavaBridgeの通信系は通信の実装によらないように設計していたので、XMLRPCの実装をごっそり 俺RPC で取り替えることで高速化を実現した。

俺RPC はデータを俺エンコード方式のバイナリで転送する。そのため、XMLRPCの遅さの原因だったテキスト変換・XMLパースが排除できて高速化できた。また、テキスト変換によって発生していた浮動少数の誤差も排除することが出来た。さらに、独自実装なだけにデータタイプも必要なだけ増やすことが出来たので、効率よく正確にデータを転送できるようになった。

ただし、RPC自体の独自完全実装はなかなか大変だった。データのエンコード・デコードは、今までのいろいろなデコーダーを実装してきた経験から、設計と実装はそれほど苦労はしなかった。一番大変なのは通信の管理で、これはまさにデッドロックとの戦いだった。なんとか効率よくトランザクションを実行して、エラーもちゃんと拾って上位レイヤーに伝えるという目標が達成できた。俺RPC は他にも使えそうなので、これだけ切り出して公開してもいいかもしれない。

通信を高速化した後、やはり通信量を減らすことも必要だということで、プロトコルの改善や重複情報のキャッシュをすることでさらに速くなった。

とりあえず、実用的な速度で動くようになったのだけども、パフォーマンスを改善しだすときりがない。膨らんだコードの半分ぐらいがこの高速化によるもの。

実装して気づいたこと

RubyとJavaのメソッドの扱いの違い

Javaは同じ名前でも引数が違えば違うメソッドとして区別されるが、Rubyでは引数で区別するということはない。なので、Ruby側でJavaのメソッドをオーバーライドした場合、同じ名前のメソッドはまとめてオーバーライドされる。

Rubyは、ブロックの最後の値が自動的にそのブロックの帰り値になってしまう。そのため、RubyでJavaのメソッドをオーバーライドした場合、意図しない場所で意図しないオブジェクトを帰り値にしてしまってIOErrorが起きてしまうことがある。実装の際には帰り値に気をつける必要がある。

RJBではメソッドシグネチャを指定することで回避しているが、個人的にはこのあたりは限りなく透過的に自動変換出来るべきだと思っている。ただ、やっぱり探索コストや選択に主観が入ってしまうので、必要ならば明示的に指定する方法もあった方がいいのかもしれないとも思っている。

Javaのメタ操作

java.util.Arrays#asList で帰ってくる List は、privateなインナークラスで実装されている。しかし、そのオブジェクトに対して直接 toArray を呼ぶと private だからということで IllegalAccessException が発生する。うまく動かすためには interface の java.util.List までたどって、呼ぶ必要がある。つまり、オブジェクトが public でない限り、public なメソッドであっても呼べないらしい。めんどくさい。別の方法としてMethodオブジェクトをsetAccessible(true)としてもOKなのだけども、それは一般的なセキュリティーマネージャで通るのか自信がない。

この問題も含めて、適切なメソッドを探してくる部分が遅いので、他の pnuts とかの処理系を研究する必要がある。バイトコードで実行してしまえば速いのかな。

さらに発生した問題

静的型 vs DuckTyping

Javaは静的に型が決まり、入れ物である変数に型がある。一方のRubyでは、変数の中身には型があるけども、入れ物である変数には型がない。この違いはかなり大きいけども、ある程度Javaも動的性質があるので乗り越えることが出来なくはない。

一番大きな問題はメソッドの検索方法。Javaはメソッド名と引数の型の情報を使ってメソッドを探す。呼びたいメソッドはある程度コンパイル時に決まる。一方、Rubyは名前だけでメソッドを検索して、引数の数が合わなければエラーになるし、引数の中身が期待したものでなければそこでもエラーになる。

Javaの静的型の性質は、コンパイル時にメソッド呼び出しの問題がほとんど解決されるため、特に大規模開発での開発の安全性が重視されるような場合には有効である。一方、Rubyは実行してみるまでそのような問題が検知できないため、型に頼りたくなるような開発には向かない。

しかし、Rubyは DuckTyping と呼ばれる柔軟な型チェックというか型認識の考え方がある。特定のメソッドを期待するためにインタフェースや抽象クラスを作らなくてもいきなりオブジェクトを渡せてしまうため、大幅にコード量が減る。特に、デザインパターンの大半が、静的型な世界でどうやって柔軟性を高めるかというアイデアであるので、DuckTypingを使えば回りくどいことをしなくても柔軟性を得られる。コード量や分かりやすさが向上すれば、型に頼らなくてもバグの量を減らすことが出来るという戦略でもある。

話を戻すと、問題はRubyにはメソッドを指定する際に型の情報がないのでJavaのメソッド検索が大変ややこしいというもの。メソッドを名前で検索した後は引数をチェックするのだけども、引数が数値の場合、今渡そうとしている数値が int なのか long なのか、あるいは float なのか double なのか調べる方法がない。そのため、数値自体を調べて必要ならば値を変換してメソッドを探す必要がある。しかも複数の候補が見つかることもある。最終的には、決定的にぴったり合うメソッドが見つからない場合は、とりあえずそれらしいものを全部リストアップして一番もっともらしいメソッドを選ぶ。このため、たまに意図しないメソッドを選んでしまうときもあるし、メソッド検索の速度もかなり遅い。でも、「意図しない」とはいえ、数値であることには変わりないのでそれなりに動くし、Javaで無理やりDuckTypingが行われているみたいで面白い。

参考:Not found: /20070123.html

Javassistのバグ?

Javassist の CtNewMethod で作った Method は、CtClassが違ってもシグニチャが同じであれば hashCode は同じ値を返し、さらに equals は true を返す。ちなみに、 == はfalseを返す。実際違うオブジェクトとして認識されるべきなのだけども、コレクションに入れてしまうと同じものだと認識されてしまう。

最初、Javaで生成されたオブジェクトを java.util.HashMap にキーとして入れていたのだけども、原因が分かるまでは意味不明のエラーが頻発して困った。

原因が分かった後、調べてみるとそういう実装は他にもありそうだったので、結局「== で false を返すオブジェクトを違うキーとして区別する俺HashMap」を作成して解決した。

さらなるパフォーマンス改善

プロファイルを取りながらいろいろやったけども、やっぱり教科書どおりの最適化しか有効ではなかった。

  • 小手先の高速化はあんまり効かない
    • 長いif・case文をtableにしてみる
      • →if文評価よりも、tableから引っ張ってきて評価する時間の方が長いらしい
    • オブジェクトをキャッシュしてみる
      • →キャッシュの検索(キーの比較)に時間がかかってキャンセルされる
  • ストリームのバッファリングはかなり効く
  • ThreadPoolは多少効く。負荷が高くなると良く効く。

他のBridge実装との比較など

同様のRuby/JavaはRAAなどを探すといろいろある。

  • rjava
  • rjb
  • rjni
  • RubyJDWP

このうち、rjavaは独自プロトコルでTCPで接続するところが似ている。RubyJDWPもTCPでつなぐのだけども、JDWPというJavaのデバッグ用プロトコルを使う。ただ、これらは単にJavaのクラスを呼び出すだけみたいなので、GUIのイベントハンドラをRubyで実装するというのは無理っぽい。時間がないので試していない。

rjbとrjniはJNIを使ってつなぐので、パフォーマンスは高い。rjbについては試したことがあるけども、スレッドの問題や、Rubyでオーバーライドできるものがinterfaceのみだったり、いちいちクラスをインポートしなければいけないところが使っていて不満だった。しかしながら、当時も今もちゃんと動くし、yajbを作ることになったきっかけにもなった。

yajbの紹介サイトなど

Bringing Ruby to the Enterprise: Enterprise Ruby Recommendations(PDF)

EnterpriseなJavaシステムとつなぐには硬くて早い rjb と、遅いけど柔軟なyajb がありますよという紹介。

Ruby for Java Programmers, Part III

JavaとRubyをつなぐシリーズ。なぜかOSX10.4でクラッシュしてぜんぜん動かなかったらしい。一応、手元の OSX10.4のRuby1.8.4でチェックしてはいるので、多分独自コンパイルRubyとかバージョンが変だったとかそういう問題ではないかと。

JRuby vs yajb

JRuby メイン開発者の Charles Nutter による yajb への感想。JRubyにも不満があって yajb を作ったということで、コメントしてもらえるだけでも大変光栄なのかもしれないが、まったく何にも調べずにあさってなコメントがついているのが悲しい。きっと移籍前で忙しかったのかな。

この当時のJRubyはGUIとか遅くてあんまり使えなかったし、普通のクラスがextendできなかったりと、かなり yajb が勝っていたポイントがあったと思う。

今年のRubyKaigi行きたかった。

LRUG Meeting: Java code & Libraries

Integrating Java with Ruby (PDF)

London Ruby User Group 主催のセミナーでの紹介。yajbでApacheFOPを操作するサンプルなど。

August NovaRUG meeting

Ruby and Java(PDF)

Northern Virginia Ruby User's Group 主催のセミナーでの紹介。JRuby, RJB, YAJB の機能の違いだけでなく、各値変換の方法やその他細かい挙動についての詳細な比較。YAJBに至っては、Class図や内部のアーキテクチャまで紹介。さらには自分も知らなかったノウハウやバグまで・・・。

Ruby から JFreeChart

もともと、自分もRubyから自作のJavaかしかツールにアクセスするのが目的だったので、こういう使い方はもっともだと思う。

Plugins - Rails XLS

yajb-POI経由でExcelを扱うRailsプラグイン。

Ruby Driver for HSQLDB

RubyからPureJavaなDBであるHSQLDBにアクセスするドライバ。本人も書いているように、遅いので実験的なもの。その後 AcriveRecord のドライバを作るとか作らないとか。

S2用コンソール

昨年のNulab社での勉強会でデモしたもの。コード(単にirb内でS2にアクセスしているだけ)は以下のよう*1。irbの補完機能を駆使して、S2内部のオブジェクトをインタラクティブに調べたり、引っ張り出して動作実験したり、いきなりRubyでインタフェースを実装してS2 に登録して実験したりするツール。S2をインタラクティブに触れたり、補完が出来るというところがちょっと受けたみたい。

# s2console.rb
# 
# 適当なところに置いて、以下のコマンドで起動。
# irb -Ku -r s2console.rb 
# 
TOMCAT_PATH = "c:/usr/java/apache-tomcat-5.5.xx"
DICON = "app.dicon"

require 'yajb/jbridge'
require 'yajb/jlambda'
jars = Dir.glob('lib/*.jar')
jars << "classes"
jars << Dir.glob("#{TOMCAT_PATH}/common/lib/*.jar")

JBRIDGE_OPTIONS = {
  :jvm_vm_args => "-Xmx256m",
  :classpath => jars.join(";"),
  :bridge_log => true
}

include JavaBridge

[
  "org.seasar.framework.container.*",
  "org.seasar.framework.container.factory.*",
].each {|i| jimport i}

@s2cutil = :s2console_S2Console.jclass

$s2 = :S2ContainerFactory.jclass.create(DICON)
$s2.init

def comp(name)
  $s2.getComponent(name)
end

def lscompdef(reg = nil)
  comps = @s2cutil.getAllComponentDefs($s2)
  return comps unless reg
  return comps.select {|i| i.getComponentName =~ reg}
end

def lscomp(reg = nil)
  comps = lscompdef(reg)
  return comps.map{|i| i.getComponentName }.compact
end
//S2Console.java
//コンパイルしてどこかクラスパスから見えるところに置いておく
package s2console;

import org.seasar.framework.container.*;
import org.seasar.framework.container.factory.*;
import java.util.ArrayList;

public class S2Console {

    public static ComponentDef[] getAllComponentDefs(S2Container s2) {
        ArrayList<ComponentDef> list = new ArrayList<ComponentDef>();
        __getAllComponentDefs(s2,list);
        return list.toArray(new ComponentDef[list.size()]);
    }

    private static void __getAllComponentDefs(S2Container s2,
                                              ArrayList<ComponentDef> list) {
        if (s2 == null) return;
        for (int i=0;i<s2.getComponentDefSize();i++) {
            list.add(s2.getComponentDef(i));
        }
        for (int i=0;i<s2.getChildSize();i++) {
            __getAllComponentDefs(s2.getChild(i),list);
        }
    }

    public static String[] getAllComponentNames(S2Container s2) {
        ComponentDef[] defs = getAllComponentDefs(s2);
        ArrayList<String> ret = new ArrayList<String>();
        for(int i=0;i<defs.length;i++) {
            String a = defs[i].getComponentName();
            if (a == null) continue;
            ret.add(a);
        }
        return ret.toArray(new String[ret.size()]);
    }

    public static String[] def2str(Object[] list) {
        String[] ret = new String[list.length];
        for (int i=0;i<list.length;i++) {
            ret[i] = ((ComponentDef)(list[i])).getComponentName();
        }
        return ret;
    }
}

yajbの今後

とりあえず、各地から送っていただいたバグ報告やパッチを取り込んで、修正版の 0.9.0 を出したいと思っている。

あと、RubyからJavaのクラスローダーを実装できるようにする拡張や、クラスパスの設定があまり賢くないという部分を直した 0.9.1 も出したい。

手元ではここまで出来ているので、あとはリリース作業だけ。

今現在実験していることは、 RJBを使ったドライバと、yajbのJava側のエンジンに複数接続が出来るようにする拡張。

複数接続については、yajbのJava側のブリッジの多言語化への第一歩で、目下の課題はWebブラウザのJavaScriptShellからサーバー側のJavaオブジェクトをインタラクティブに扱うこと。ブラウザからアクセスしてサーバー側のモニターにSwingが出たら勝ちとか。

コードはダウンロードできるけども、Koderに勝手に登録されて非常に見やすくなっている。すごい便利。

YAJB : Koders - Source Code Search Engine

ところで、Development Cost に $149,740 とか書いてあるけど・・・。


いろいろやりたい。楽しいコードをたくさん書きたい。でも今は出来ない。

*1:これらのコードについて動かし方などは説明してません。雰囲気だけどうぞ。