JARファイルは、複数のファイルを一つにまとめた、プラットフォームに依存しない、 ファイルの形式です。ですから、UNIX上で開発されたJARファイルを、NTで利用すること も、その逆も、簡単に出来ます。こうしたプラットフォーム独立という性格は、もと もとJavaのクラス・ファイル自身が持っていたものですが、JARファイルは、複数の appletとそれに必要なクラス・ファイル、画像・音声データ等の多くのファイルを 一つにまとめることで、HTTPのアクセス・スピードを大きく改善することが出来る ようになりました。JARファイルは、ポピュラーなZIPファイルの形式ですので、 データの圧縮も行われ、ネットワークの負荷の改善は、さらに大きなものになりま した。それに加えて、JARでは、データ作成時に、ディジタル・サインを付け加える 機能もあり、データのセキュリティを高めるのに利用できます。
appletでJARファイルを利用するには、appletタグの中で、新しく導入された ARCHIVE パラメータを使います。ここでのディレクトリの指定は、HTMLのページがある ディレクトリからの相対指定です。いままで使われてきた、code=xxxx.class という 指定は、引き続き有効です。
<applet code=Animator.class archive="jars/animator.jar" width=460 height=160> <param name=foo value="bar"> </applet>
次の例は、archiveに、複数のJARファイルを指定したものです。 このように、コンマで区切って、複数のJARファイルを利用することが出来ます。
<applet code=Animator.class archive="classes.jar , images.jar , sounds.jar" width=460 height=160> <param name=foo value="bar"> </applet>
いくつかのファイルから、JARファイルをつくるには、jarコマンドを使います。 jarコマンドは、UNIXで良く使われる tar コマンドと同じオプションが使えます。 jarコマンドの第一引数には、次のオプションの組み合わせを指定する必要があります。
c JARファイルを生成(Create)します。f指定がなければ、標準出力へ。 t JARファイルの中身のテーブル(Table)を表示します。f指定がなければ、 標準入力の内容のテーブルを表示します。 x JARファイルから元のファイルを引き出し(eXtract)します。 f 第二引数が、c,t,x の操作の対象となるJARファイルとなります。 v Verbosオプションです。
これらのオプションとは別に、jarコマンドの引数には、三つの種類のファイルが 登場します。
典型的な使い方は、次のようなものです。
jar cf myjarfile *.class
この例では、カレント・ディレクトリのクラス・ファイルがすべて、 myjarfileにアルカイーブされます。この時、manifestファイルが、デフォールト では、META-INF/MANIFEST.INFという名前で、自動的に生成されて、JARファイルの 一番先頭に格納されます。manifestファイルというのは、アルカイーブのメタ情報 が置かれる場所です。
次の例は、あらかじめmanifestファイルを持っている場合の指定の仕方です。 第一引数のオプション中の m が、manifestファイルを表わします。
jar cmf myManifestFile myjarfile *.class
実際に、アルカイーブを作成し、それを展開したコマンドの例を、紹介してみたいと 思います。 ここでは、voterディレクトリの中のクラス・ファイルを、voter.jarというJAR ファイルにまとめています。オプションcvf中のvはverbosという意味で、多少、 コマンドからのメッセージが多くなっています。
============================================================================= bash$ cd voter bash$ ls DemoVoter$VetoableChangeAdapter.class DemoVoter.class DemoVoter.BAK DemoVoter.java bash$ jar cvf voter.jar *.class adding: DemoVoter$VetoableChangeAdapter.class (in=709) (out=438) (deflated 38%) adding: DemoVoter.class (in=1293) (out=731) (deflated 43%) =============================================================================
次は、tオプションを用いて、JARファイルの中身をチェックしています。JARファイルの 中身のリストが表示されるだけで、ファイルの展開は行われません。JARファイルの 中身を確認したい時には、便利なコマンドです。
============================================================================= bash$ jar tvf voter.jar 315 Sat Jul 26 17:06:40 GMT+01:00 1997 META-INF/MANIFEST.MF 709 Sat Jul 26 09:41:32 GMT+01:00 1997 DemoVoter$VetoableChangeAdapter.class 1293 Sat Jul 26 09:41:32 GMT+01:00 1997 DemoVoter.class =============================================================================
最後は、xオプションで、JARファイルを展開するという例です。 ここでは、カレント・ディレクトリに、実際にクラス・ファイルが展開される だけでなく、META-INFという名前のディレクトリが作られ、その下に、 MANIFEST.MFという名前のmanifestファイルが作られます。
============================================================================= bash$ jar xvf voter.jar extracted: META-INF/MANIFEST.MF extracted: DemoVoter$VetoableChangeAdapter.class extracted: DemoVoter.class =============================================================================
ここで展開された実際のmanifestファイルの中身を、次に示します。
最初に気づくのは、全ての情報が、名前:値 という形式であるということです。 先頭の、Manifest-Version: 1.0 は、manifestファイルのバージョンを、 その後には、アルカイーブされたファイル毎の情報が、空行で区切られて 並んでいます。Name: はもちろん、ファイルの名前です。
bash$ cat META-INF/MANIFEST.MF Manifest-Version: 1.0 Name: DemoVoter$VetoableChangeAdapter.class Digest-Algorithms: SHA MD5 SHA-Digest: Z3CvW69d6gwujCg3I618kgpitNI= MD5-Digest: 9RTk/w5A4R8+L6DBSTvi/Q== Name: DemoVoter.class Digest-Algorithms: SHA MD5 SHA-Digest: pZiwxyPR9oSmxe+F8cF9TBNNcyg= MD5-Digest: H7qGU258WL7CjLAGeMWK2Q==
manifestファイルの中で目立つのは、Digestに関わる情報です。 ファイルのDigestとは、文字通りファイルの要約なのですが、意味的な要約 ではなく、ファイルにハッシュ・コードを与えるものと考えてください。 内容が同じファイルは、必ず、同じDigestを持ちます。また、Digestがことなる ファイルは、明確に、異なるファイルです。ハッシュであるという性質上、 異なるファイルでも、同じDigest=ハッシュ・コードを持つことは、ありえますが、 ハッシュの空間を巨大なものにとれば、そうした可能性を大きく減らすことが 出来ます。
Digest-Algorithms: SHA MD5
というエントリは、このDigestを計算する
アルゴリズムが、SHAとMD5の二種類であることを表わし、
SHA-Digest/MD5-Digestは、それぞれのアルゴリズムで計算した、ファイルの
Digestを表わしています。これらの値は、一般には、決まったbit数のbinaryなの
ですが、manifestファイルは、人間にとっても可読なフォーマットですので、
そのbit列を、BASE64を用いて、読める形に変換しています。
次の例では、コピーで作ったファイルは同じDigestを持つこと、ファイルをほんの 少し変えただけで、Digestが、全く変わってしまうことを示しています。
============================================================================= bash$ cp 1i18n.txt a.txt bash$ cp a.txt b.txt bash$ echo b >> b.txt bash$ jar cvf voter2.jar *.txt bash$ jar xvf voter2.jar bash$ cat META-INF/MANIFEST.MF Manifest-Version: 1.0 Name: 1i18n.txt Digest-Algorithms: SHA MD5 SHA-Digest: A92/ZBxFmOK1y7SEe6BHvatOgfo= MD5-Digest: RyMjjMpnYm37qvPot4wDXw== Name: a.txt Digest-Algorithms: SHA MD5 SHA-Digest: A92/ZBxFmOK1y7SEe6BHvatOgfo= MD5-Digest: RyMjjMpnYm37qvPot4wDXw== Name: b.txt Digest-Algorithms: SHA MD5 SHA-Digest: t+1T+7O4T8pBnmLmdaHVfnF1Iyk= MD5-Digest: tiHq7x/uVmEdEWnzb9CBQA== bash$ =============================================================================
beansでは、これらのエントリに加えて、
Java-Bean: True
というエントリが必要です。
次の例は、beansのdemoディレクトリに、サンプルとして提供されている、 makeファイルの一部ですが、manifest.tmpというファイルが、Here Document の形で与えられています。
こうした方法で、一つのJARファイルに、沢山のbeansを詰め込むことも可能と なります。あらかじめ与えられるmanifestファイルに、VersionやDigestの情報が 無いのは当然で、それらはユーザが与えるのではなく、JARコマン自身によって 生成されるものだからです。
こうして作られたJARファイルの manifestファイルには、もちろん、これらの 情報が追加されることになります。
============================================================================= # Create a JAR file with a suitable manifest. $(JARFILE): $(CLASSFILES) $(DATAFILES) jar cfm $(JARFILE) <<manifest.tmp sunw\demo\buttons\*.class $(DATAFILES) Name: sunw/demo/buttons/ExplicitButton.class Java-Bean: True Name: sunw/demo/buttons/OurButton.class Java-Bean: True Name: sunw/demo/buttons/OrangeButton.ser Java-Bean: True Name: sunw/demo/buttons/BlueButton.ser Java-Bean: True << =============================================================================
BeanBoxでbeansは、JARファイルの形で提供されています。 BeanBoxを起動したときに、左側に beansの一覧である toolboxが 表示されますが、それは、BeanBoxがあるディレクトリ上のJARファイルを 読み込んで生成しています。
次のリストで処理のおおざっぱな流れが分かると思います。 ToolBoxPanelは、getJarsNamesメソッドで読み込むべきJARファイルを ベクターに収め、それぞれについてaddBeansInJarメソッドを呼び出します。 このメソッドは、JarLoaderクラスのloadJarDoOnBeanメソッドを呼びます。 loadJarDoOnBeanは、内部で、先に見たloadJarメソッドを呼び出します。
============================================================================= class ToolBoxPanel extends Panel implements Runnable, MouseListener { private class Helper implements DoOnBean { ..... } private Helper helper = new Helper(); Vector beanLabels = new Vector(); Vector beanNames = new Vector(); Vector beanIcons = new Vector(); Vector beanJars = new Vector(); ...... ToolBoxPanel(Frame frame) { ...... Vector jarNames = getJarNames(); for (int i = 0; i < jarNames.size(); i++) { String name = (String)jarNames.elementAt(i); addBeansInJar(name); } insertThread = new Thread(this); insertThread.start(); } synchronized void addBeansInJar(String jarFile) { JarLoader.loadJarDoOnBean(jarFile, helper); doLayout(); } ...... ...... } =============================================================================
============================================================================= public static void loadJarDoOnBean(String jarFile, DoOnBean action) { JarLoader jl = null; try { jl = new JarLoader(jarFile); if (jl == null) { System.err.println("file name passed is not a valid JarFile"); return; } } catch (Exception ex) { System.err.println("caught an exception while trying to load "+jarFile); ex.printStackTrace(); return; } JarInfo ji = jl.loadJar(); if (ji.getCount() == 0) { System.err.println("JAR file "+jarFile+" didn't have any beans!"); System.err.println("You should provide an explicit manifest file when creating the JAR,"); System.err.println("specifying which files represent beans.\n"); } for (int i=0; i<ji.getCount(); i++) { String beanName = ji.getName(i); BeanInfo bi = ji.getBeanInfo(i); if (bi == null) { // We couldn't load the bean. continue; } action.action(ji, bi, beanName); } } =============================================================================
次のBeanBoxのfileメニュー中の loadJarないしloadも、JARファイルを対象とします。 これらの働きも、追跡してみてください。
======================================================== public class BeanBox extends Panel implements Serializable, Runnable, MouseListener, MouseMotionListener { /** * This implements the "loadJar" menu item. * Load the contents of a JAR file */ private void loadJar() { FileDialog fd = new FileDialog(getFrame(), "Load from JAR File", FileDialog.LOAD); // the setDirectory() is not needed, except for a bug under Solaris... fd.setDirectory(System.getProperty("user.dir")); fd.setFile(defaultStoreFile); fd.show(); String fname = fd.getFile(); if (fname == null) { return; } String dname = fd.getDirectory(); File file = new File(dname, fname); BeanBoxFrame.getToolBox().addBeansInJar(file.getPath()); } /** * This implements the "load" menu item. This loads the BeanBox state * from a named file. */ private void load() { removeAll(); FileDialog fd = new FileDialog(getFrame(), "Load File", FileDialog.LOAD); // needed for a bug under Solaris... fd.setDirectory(System.getProperty("user.dir")); fd.setFile(defaultStoreFile); fd.show(); String fname = fd.getFile(); if (fname == null) { return; } String dname = fd.getDirectory(); File file = new File(dname, fname); JarLoader jl = null; try { jl = new JarLoader(file.getPath()); if (jl == null) { System.err.println("file name passed is not a valid JarFile"); return; } } catch (Exception ex) { System.err.println("caught an exception while trying to load "+file.getPath()); ex.printStackTrace(); return; } JarInfo ji = jl.loadJar(); // OK, loaded all the classes -- now, instantiate them... try { // get the one object as a bean... ClassLoader cl = jl.getLoader(); InputStream is = cl.getResourceAsStream(serFileName(null).replace(File.separatorChar, '/')); ObjectInputStream ois = new ObjectInputStreamWithLoader(is, cl); // Read all the contained beans into this BeanBox. readContents(ois); } catch (Exception ex) { ex.printStackTrace(); return; } } } =========================================================