BeanBoxでは、serializationの機能がどのように使われているのかを見てみましょう。 最初に、BeanBox内で選択したbeanをcopy/cut/pasteする機能があります。 この操作でのクリップボードの実体は、"tmp/beanBoxClip.ser"というファイルに 他なりません。copy/cutの場合には、このファイルに選択されたbeanがserialize され、pasteの場合には、このファイルからdeserializeされることになります。
特に難しいところはありません。 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は、基本的な部分は、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は、すこし複雑なように見えますが、大事なところは、リスト中の 次の二つのシーケンスです。
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コマンドは、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
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ファイルの働きを見るまで、残しておくことに しましょう。