next up previous contents
Next: Class LoaderとJar Fileの利用 Up: Serialization Previous: 自前のメソッド

BeanBoxでのSerファイルの利用

BeanBoxでは、serializationの機能がどのように使われているのかを見てみましょう。 最初に、BeanBox内で選択したbeanをcopy/cut/pasteする機能があります。 この操作でのクリップボードの実体は、"tmp/beanBoxClip.ser"というファイルに 他なりません。copy/cutの場合には、このファイルに選択されたbeanがserialize され、pasteの場合には、このファイルからdeserializeされることになります。

copy

特に難しいところはありません。 getClipFileNameメソッドはクリップボードとなる ファイル名を返します。このファイルを、FileOutputStreamで開いて、さらに、 ObjectOutputStream に変換します。あとは、copy/cutが、beanそのものではなく、 そのwrapperを対象にしていることに注意してください。

==========================================================================
    /**
     * Serialize the current focus bean to our clipboard file.
     */
    private void copy() {
        Wrapper wrapper = BeanBoxFrame.getCurrentWrapper();

        BeanBoxFrame.setClipLabel(null);
        try {
            File f = new File(BeanBoxFrame.getClipFileName());
            File dir = new File(f.getParent());
            dir.mkdirs();
            FileOutputStream fos = new FileOutputStream(f);
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            // Ask the Wrapper to serialize the "naked" bean.
            // This causes the Wrapper to remove all listeners
            // before serializing, and add them back after.
            wrapper.writeNakedBean(oos);
            oos.close();
            fos.close();
            BeanBoxFrame.setClipLabel(wrapper.getBeanName());
        } catch (Exception ex) {
            error("Copy failed", ex);
           }

    }

==========================================================================

内容的な処理は、全てwrapperクラスのwriteNakedBeanで行われます。 この時、いったん全てのリスナーが切り離されてからクリップボードへの 書き込みが行われ、その後で、リスナーが回復されています。 ですから、wiringであるイベント処理を担っているbeanをコピーしても、 beanの実体はコピーされますが、そのイベント処理機能はコピーされるわけでは ありません。

    void writeNakedBean(ObjectOutputStream oos) throws IOException {

        // First, detach all event listeners.
        removeListeners();
                                
        // Now write the bean.
        oos.writeObject(bean);

        // Now rettach all event listeners.
        attachListeners();
    }

removeListenersもattachListenersも、reflectionの機能を使って、add/remove Listenerメソッドを呼び出します。次のremoveListenersメソッドの コードを解読してみてください。

==========================================================================
   /**
     * Temporarily remove any event listeners.
     */

    void removeListeners() {
        Enumeration enum = eventTargets.elements();
        while (enum.hasMoreElements()) {
            WrapperEventTarget et = (WrapperEventTarget)enum.nextElement();
            EventSetDescriptor esd = (EventSetDescriptor) esdMap.get(et.eventSetName);
            Method remover = esd.getRemoveListenerMethod();
            try {
                Object args[] = { et.targetListener };
                remover.invoke(bean, args);
            } catch (Exception ex) {
                System.err.println("Wrapper: removing event listener for "
                                         + et.eventSetName + " failed:");
                System.err.println("    " + ex);
                ex.printStackTrace();
            }
        }
        // Remove mouse listeners.
        listenForMice(false);
    }
==========================================================================

cut

クリップボードへのcutは、基本的な部分は、copyと同じです。 wrapperの親にあたるComponentから、そのwrapperをremoveします。 cleanupメソッドでは、removeListenersメソッドが呼び出されて wiringの 後始末が行われます。

==========================================================================
    /**
     * Serialize the current focus bean to our clipboard file, and
     * then remove it from the BeanBox.
     */

    private void cut() {
        Wrapper wrapper = BeanBoxFrame.getCurrentWrapper();
        Object bean = wrapper.getBean();
        if (bean != BeanBoxFrame.getTopBox()) {
            copy();
            Container parent = wrapper.getParent();
            BeanBoxFrame.setCurrentComponent(null);
            if (parent != null) {
                parent.remove(wrapper);
            }
            wrapper.cleanup();
        }
    }

    /**
     * Cleanup is called when a Wrapper has been "cut" from the BeanBox.
     */
    void cleanup() {
        if (bean instanceof Applet) {
            Applet apl = (Applet)bean;
            apl.stop();
            apl.destroy();
        }
        removeListeners();        
        // We should also remove ourself from any event sources...
    }
==========================================================================

paste

pasteは、すこし複雑なように見えますが、大事なところは、リスト中の 次の二つのシーケンスです。

            Object bean = Beans.instantiate(loader, clipName);
            doInsert(bean, beanLabel, true);

クラス・ローダを作って、クリップボードから読み出すというのが基本です。 MyProducerは、ほとんど、与えられた引数をFileInputStreamでオープンするのと 変わりありません。BeanBoxFrameクラスのソースを読めば、clipNameには、 "beanBoxClip"という文字列が入ることも簡単に分かります。 setLocalResourceSourceは、結局、ファイル tmp/beanBoxClip.ser のInputStreamを "beanBoxClip.ser"という名前で、loaderに登録します。

==========================================================================
    /**
     * write in a bean from our clipboard file and insert it into
     * the BeanBox.
     */
    private void paste() {
        synchronized (this) {
            mouseClickEvent = null;
        }
        try {
            // Set the insert cursor before writeing the clipboard.
            setCursor(crosshairCursor);

            SimpleClassLoader loader = SimpleClassLoader.ourLoader;
            MyProducer p = new MyProducer(BeanBoxFrame.getClipFileName());
            String clipName = BeanBoxFrame.getClipResource();

            loader.setLocalResourceSource(clipName+".ser", p);
            Object bean = Beans.instantiate(loader, clipName);
            String beanLabel = BeanBoxFrame.getClipLabel();
            doInsert(bean, beanLabel, true);
        } catch (Exception ex) {
            error("Paste failed", ex);
           }
    }
==========================================================================

ここでは、先に登録された "beanBoxClip.ser"から、InputStreamが回復されて、 それからObjectInputStreamが作られ(正確には、それを拡大した ObjectInputStreamWithLoaderです)、そこから readObjectで、もとのオブジェクトが 復活します。

doInsertメソッドの働きについては、ソースをよく読んでください。

==========================================================================
public class Beans {
    .....
    public static Object instantiate(ClassLoader cls, String beanName) 
                        throws java.io.IOException, ClassNotFoundException {
        java.io.InputStream ins;
        java.io.ObjectInputStream oins = null;
        Object result = null;
        boolean serialized = false;

        // Try to find a serialized object with this name
        String serName = beanName.replace('.','/').concat(".ser");
        if (cls == null) {
            ins = ClassLoader.getSystemResourceAsStream(serName);
        } else {
            ins  = cls.getResourceAsStream(serName);
        }
        if (ins != null) {
            try {
                if (cls == null) {
                    oins = new ObjectInputStream(ins);
                } else {
                    oins = new ObjectInputStreamWithLoader(ins, cls);
                }
                result = oins.readObject();
                serialized = true;
                oins.close();
            } catch (java.io.IOException ex) {
                ins.close();
                // For now, drop through and try opening the class.
                // throw ex;
            } catch (ClassNotFoundException ex) {
                ins.close();
                throw ex;
            }
        }
        ........
        ........
    }
    ........
    ........
}
==========================================================================

save

saveコマンドは、BeanBox全体の状態をFileDialogで指定したファイル上に保存します。 今まで見てきたcopy/cutとは異なって、イベントのwiringに伴う情報も、切り捨てずに 全て保存します。

そのためにwriteContentsメソッドで、BeanBox上の全てのComponentを一つのSer ファイルに書き出すとともに、イベントのwiringで生じた hookupファイル と呼ばれる Javaのクラス・ファイル達を、先に作成したSerファイルとともに、一つのJARファイルに束ねます。この時、jarコマンドがプログラムの中から呼ばれています。

もし、BeanBox中のComponentを書き出したSerファイルがcomponent.ser ファイル、 hookupファイルが、hookup1.class, hookup2.classの二つで、保存ファイルが、 save.jarとすると、saveメソッド内では、次のようなjarコマンドが実行されることに なります。

     jar cf save.jar compnent.ser hookup1.class hookup2.class

==========================================================================
    public void save() {
        boolean saveAdaptors = true;

        FileDialog fd = new FileDialog(getFrame(),"Save File",FileDialog.SAVE);
        // 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);            

        try {
            // create the single ObjectOutputStream
            File serFile = new File(serFileName(null));
            File dir = new File(serFile.getParent());
            if (dir != null) {
                dir.mkdirs();
            }
            FileOutputStream f = new FileOutputStream(serFile);
            ObjectOutputStream oos = new ObjectOutputStream(f);
            writeContents(oos);
            oos.close();

            Vector hfiles = new Vector(0);        // hookups
            if (saveAdaptors) {
                hfiles = HookupManager.getHookupFiles();
            }
            String args[] = new String[3+hfiles.size()];

            args[0] = "cf";
            args[1] = file.getPath();        // name for the JAR file
            // no manifest file - so far
            args[2] = serFileName(null);
            for (int i=0; i<hfiles.size(); i++) {
                args[3+i] = (String) hfiles.elementAt(i);
            }

            sun.tools.jar.Main jartool =
                new sun.tools.jar.Main(System.out, System.err, "jar");

            boolean ok = jartool.run(args);
            if (!ok) {
                error("Jar tool invocation failed");
            }

        } catch (Exception ex) {
            error("Save failed", ex);
           }
    }
==========================================================================

ここではsave.jarとした JARファイルは、FileDialogから自由に指定できますが、 Serファイルやhookupファイルは、システムによって、特定のディレクトリに特定の 名前で生成されます。

   Serファイル           tmp/___comp_ROOT.ser
   Hookupファイル        tmp/sun/beanbox/___Hookup_<ID>.class
                         tmp/sun/beanbox/___Hookup_1430e94994.class

load

saveされたBeanBoxの状態を元に戻すのが loadメソッドです。 ここでは、JarLoaderが活躍しています。

==========================================================================
    /**
     * 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);

            // write all the contained beans into this BeanBox.
            writeContents(ois);

        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }
==========================================================================

この部分の理解は、次章のJarファイルの働きを見るまで、残しておくことに しましょう。



maruyama@wakhok.ac.jp