next up previous contents
Next: JDK1.1での日本語処理 Up: Class LoaderとJar Fileの利用 Previous: BeanBoxのSimpleClassLoader

JARファイル

JARファイルは、複数のファイルを一つにまとめた、プラットフォームに依存しない、 ファイルの形式です。ですから、UNIX上で開発されたJARファイルを、NTで利用すること も、その逆も、簡単に出来ます。こうしたプラットフォーム独立という性格は、もと もとJavaのクラス・ファイル自身が持っていたものですが、JARファイルは、複数の appletとそれに必要なクラス・ファイル、画像・音声データ等の多くのファイルを 一つにまとめることで、HTTPのアクセス・スピードを大きく改善することが出来る ようになりました。JARファイルは、ポピュラーなZIPファイルの形式ですので、 データの圧縮も行われ、ネットワークの負荷の改善は、さらに大きなものになりま した。それに加えて、JARでは、データ作成時に、ディジタル・サインを付け加える 機能もあり、データのセキュリティを高めるのに利用できます。

Appletでの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コマンドを使います。 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ファイルの中身を、次に示します。

最初に気づくのは、全ての情報が、名前:値 という形式であるということです。 先頭の、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==

Digest

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とmanifestファイル

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でのJARファイルの利用

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


}
=========================================================



maruyama@wakhok.ac.jp