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ファイルの働きを見るまで、残しておくことに しましょう。