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;
}
}
}
=========================================================