Diary/2007-4-19
Jarのマニフェストをいじる
マニフェスト中の、特定のキーの値が欲しいとき
Manifest manifest = jf.getManifest(); Attributes ma = manifest.getMainAttributes(); String classpath = (String)(ma.getValue("Class-Path"));
COINSを実行可能JARから利用する
実行可能なJarファイルでは、manifestのClass-Pathで必要なライブラリへの
パスを通すことができる。
したがって、COINSを自分でつくったアプリケーションから使う場合、
COINSをビルドしてjarにしたファイル
(COINSで配布されているjarではなくてclassesの下だけをjarにしたもの)
に対して、manifestでclass-pathを指定すればよいはずである。
が、Unix系(Linux、FreeBSD、MacOSX)ではうまく動かなかった。
# なぜかWindowsXPではうまく動いた。
これは、manifestの中のClass-PathがJavaのプロパティであるjava.class.pathに
含まれないために、COINSの中でクラスを動的にロードしようとしても
当該クラスをみつけることができないことに起因していた。
というわけで、これを回避するために、ちょっとハックしてみた。
coins-1.4.2.2-ja-searchClass_resursive.patch(756)
COINSのディレクトリの中で、
patch -p0 < coins-1.4.2.2-ja-searchClass_resursive.patch
とすれば適用できる。
COINSを実行可能JARから利用するための作業工程
たとえば、
wallet.jar lib/coins.jar
というようなディレクトリ構成にしていて
wallet.jarは実行可能jarファイルで、manifestファイルで次のように
Manifest-Version: 1.0 Main-Class: net.wasamon.wallet.WalletGUI Class-Path: lib/coins.jar
Class-Pathにlib/coins.jarをちゃんと指定していても
java -jar wallet.jar
としてLinuxやFreeBD、MacOSXで起動しようとすると
次のような例外が発生して実行できない。
ちなみにWindowsでダブルクリックするとなぜか実行できる。
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: coins.driver.SuffixFactory at coins.driver.CompilerDriver.initializeSuffixFactory(CompilerDriver.java:81) at coins.driver.CompilerDriver.<init>(CompilerDriver.java:184) at net.wasamon.wallet.WalletDriver.doCompile(WalletDriver.java:156) at net.wasamon.wallet.WalletGUI.actionPerformed(WalletGUI.java:204)
ちなみに、
java -cp wallet.jar:lib/coins.jar net.wasamon.wallet.WalletGUI
とした場合には、ちゃんと実行できる。
もちろん、coins.driver.SuffixFactoryはcoins.jarに含まれている。
で、みてみると、coins.driver.CompilerDriver.initializeSuffixFactoryで
getAttachedFileInputStream(Class String)という関数を呼び出していて、
このメソッドが原因となる例外を発生している。
このメソッドでは、
System.getProperty("java.class.path")で得られる
パスから、該当するクラスを検索するために
ディレクトリあるいは、jarファイルであっても求めるクラスを探す
searchClassメソッドを呼び出し、この戻り値が全て偽の場合に、例外が投げられる。
ここで問題となるのは、
java -jar wallet.jar
として起動した場合には、System.getProperty("java.class.path")で得られるのが
wallet.jarだけであるということ。
たしかに、これじゃあ、coins.jarの方は検索していないわけだから見つかるはずもない。
なのでmanifestで指定したClass-Pathファイルについても検索してもらう必要がある。
たとえば、こんな感じ。
Manifest manifest = jf.getManifest(); Attributes ma = manifest.getMainAttributes(); String classpath = (String)(ma.getValue("Class-Path"));
Attrubutesには、get(Object)というメソッドもあるが、getValue(String)でないとダメ。
で後は、ここで得られたclasspathを空白で分割して、
それぞれについて再帰的にsearchClassを検索すればいい。
幅優先で探索するべきなのかもしれないけど、まあ、とりあえず深さ優先で。
ちなみに、オリジナルのsearchClassは、真偽値を返すメソッドだが、
これでは、新しくmanifest中のClass-Pathから得たパスを返すことができないので、
真の代わりにパスを文字列で返し、偽の場合はnullを返すようにした。