Serialize
Java Data Objects to XML
Save and Restore Java data objects to a compact, robust, and reusable XML form
by Charles D. Havener
One of the least publicized new features of the Java Development Kit (JDK) 1.4 is the
java.beans.XMLEncoder/XMLDecoder, which is built into the java.beans package. At a stroke this
feature makes it possible to easily save and restore Java data objects, graphs of objects, and GUI
state to a compact XML form that is so robust it continues to be usable even when the original
class definitions change. There is nothing to download and install. It certainly saves JavaBeans,
but don't be misled—it knows about collections, arrays, and many common nonbean data structures.
You can also describe the data structure of your own or other third-party library classes to the
encoder, using persistence delegates, and it can save those nonbean
objects, too.
It doesn't take much to be a bean. A class just needs a public constructor that takes no
arguments and get and set methods for each significant property such that a property named foo will
have getFoo() and setFoo() methods. Technically, it must just support introspection, but that is
usually achieved by using the get and set naming convention. Here is a simple Stuff bean for our
experiments that has a String and an integer for properties:
package xmlpersist;public class Stuff {
private int k = 1;
private String s = "hello"; public Stuff() {}
public int getK() {return k;}
public String getS(){return s;}
public void setK(int i) {k=i;}
public void setS(String t){s=t;}
}
Having the persistence APIs handy will be useful to follow the examples and listings. They can
be found in many places online (see
Resources
). The simple program to serialize the Stuff bean to XML and retrieve it is shown in
Listing 1
. The decoder just instantiates each object and invokes the standard bean public method calls
setK and setS for the named properties k and s, passing them the arguments. Here is the emitted XML
file:
<?xml version=
"1.0" encoding="UTF-8"?>
<java version=
"1.4.1" class=
"java.beans.XMLDecoder">
<object class="xmlpersist.Stuff">
<void property="k">
<int>3</int>
</void>
<void property="s">
<string>goodbye</string>
</void>
</object>
</java>
If we edit the XML file so the property name is x instead of s, to simulate an old program
reading an ostensibly newer XML file:
<void property="x">
<string>goodbye</string>
</void>
then the XMLDecoder emits an exception warning to System.err and continues. Your users will
see:
"java.lang.NoSuchMethodException:
<unbound>=Stuff0.setX(
"goodbye"); Continuing ... "
The ReadStuffBetter program (see
Listing 2
) and this WarningListener:
package xmlpersist;import java.beans.
ExceptionListener;public class WarningListener
implements ExceptionListener {
public void exceptionThrown(
Exception e) {
System.out.println(
"Time to update your software");
}
}
show how to trap these messages from the decoder with a listener; then you can present your
users with a more relevant message like "Time to upgrade!" No doubt, you'll want to log the message
in some way. Encoder errors usually mean that what you're saving doesn't have an empty constructor
or follow other bean conventions. You can listen to the encoder, too, but once you've debugged the
encoding it seems more likely in the field that errors will occur during decoding as various
versions are in use. The error listener must be passed in as an argument to the decoder
constructor. The middle argument is the owner of the stream and sometimes used for calling methods
on the owner during the decoding process. It is not relevant for this example and can be set to
null.
Save Collections
If you have a set of old XML files that were created by different users and you cannot easily
create them in the "new and improved" format, you can still read in the old ones. Just ignore
warning messages and then write them out in the new format. If necessary, you could write an
upgrade program using standard XML parsers to read in the old XML format and convert it to the new
format.
Java Collections aren't beans, but the encoder knows about them and can save them easily. The
LessSimple.java example (see
Listing 3
) shows how to save items in a HashMap, which produces this XML file:
<?xml version="1.0" encoding=
"UTF-8"?>
<java version="1.4.1" class=
"java.beans.XMLDecoder">
<object class=
"java.util.HashMap">
<void method="put">
<string>item1</string>
<object id="Stuff0" class=
"xmlpersist.Stuff">
<void property="k">
<int>3</int>
</void>
<void property="s">
<string>goodbye</string>
</void>
</object>
</void>
<void method="put">
<string>item2</string>
<object idref="Stuff0"/>
</void>
</object>
</java>
Note that the same "stuff" object was added to the HashMap twice (as indicated in bold), but the
XML shows it was saved once with an object id. This use of object id references is important, not
only to save space, but because it prevents circular references such as those that might occur in
graphs from blowing up because of infinite recursion. The persistence mechanism also knows how to
save ordinary arrays and Vectors.
The fact that the XML text can be stored in a varchar instead of a BLOB in a SQL database is a
significant plus, and you can see how to use a ByteArrayOutputStream (see
Listing 4
). The toString() method would produce a String of the XML, which can be written into a
varchar or text field in a database. When you read it back, use the getBytes() method on the String
object as the argument to the ByteArrayInputStream constructor and decode normally.
The persistence mechanism uses introspection to determine what to save from a regular JavaBean.
Since time immemorial, long before JDK 1.4, there has been an Introspector class with static
methods for obtaining BeanInfo objects. Therefore, if some object you want to save is not quite a
bean because it has nonstandard get and set methods, you can create a [classname]BeanInfo class as
part of your package. Its name is always the name of your class with BeanInfo appended. You don't
have to register it with the Introspector. If it is in your package, that is, in a jar file you
distribute, the Introspector will find it. Bean info classes can contain a lot of information that
isn't relevant to persistence, and they can be tedious to write.
If you use an integrated development environment (IDE) you may have a wizard that will prepare
BeanInfo classes for you that can be edited easily. Suppose the Stuff class also had a double named
d as a property but had what() and force() methods instead of the getD() and setD() methods.
Listing 5
shows part of the StuffBeanInfo class generated by Borland's JBuilder with the what() and
force() methods added. Note that if you rely on a custom BeanInfo class when the XMLEncoder runs,
then you should be sure the BeanInfo class is also available to whatever program runs the
XMLDecoder. An alternative to the StuffBeanInfo is the StuffPersistenceDelegate in
Listing 6
that will be explained shortly. For objects that are not even close to being a bean, we'll use
the full power of persistence delegates.
Complex Designs
The encoder knows how to save many nonbean classes by using built-in metadata information
about the class. The information is kept in a table of persistence delegates. The source file, on
Windows, for this list of built-ins can be found in the file MetaData.java in the src.zip archive
that can be found in the root directory of JDK 1.4 after you install it. Use WinZip or another zip
file examiner to look at the file. The built-in delegates can serve as examples for those you might
write or as guides to understanding any problems that may occur for saving complex GUI designs.
If you try to save an object that is not a bean and does not already have a built-in persistence
delegate, the encoder may save nothing but the class name. There will be no error message, but
looking at the XML file or trying to restore it will reveal that the state of the object wasn't
saved. You may create PersistenceDelegate objects and add them to the static metadata through any
XMLEncoder object. For example:
XMLEncoder e = new XMLEncoder(
System.out);
followed by several e.setPersistenceDelegate(…) actions will register any special classes you
need to save. All subsequent encoders in your application will know about the registered classes.
The initial encoder object doesn't have to be kept around.
Think of the delegates as little programs that will save an object and then restore an object
through any of its public access points from the data in the XML file. The delegates are only
needed on the encoder because the program that reads in the XML file gets all the information it
needs from that XML file. To say that this mechanism serializes the java data object doesn't do it
justice. Much more is happening. During the encoding step it interprets a little program you write
that tells it how to save and restore an object of a given class type. That little program is
composed of Statements and Expressions in the persistence delegate. The encoder runs and emits some
bytecodes that happen to be XML.
During the decoding step, the little program that is now in XML form is interpreted, and the
object is restored by following the recipe that the encoder originally wrote. The little program
you write in a custom persistence delegate can take advantage of the DefaultPersistenceDelegate by
extending it, and then you only have to tell it about things it can't figure out through
introspection, which is what the StuffPersistenceDelegate does (see
Listing 6
).
The decoder restores an object in two steps. It instantiates the object, called the new
instance, by using some constructor—a null constructor if it is a true bean—and then initializes it
by applying a sequence of public method calls to mutate the new instance into something resembling
the old instance that was originally saved. The public method calls are specified with a series of
Statement objects that are written to the encoder object, not the output stream, so that it
henceforth knows how to save objects of the specified type. Expression objects are just statements
that can return a value. They invoke public methods that return values. The most common use of
Expression statements is to invoke constructors that take arguments with the resulting value being
the new instance.
Real-World Example
I have used a free Java graph package to produce an intermediate representation of digital
circuit interconnections for a hardware simulator accelerator. Digital circuits can be specified by
the Verilog language. I have written a Verilog compiler (in Java, of course) that produces large
graphs that easily fill all available memory. For incremental module compilation and large designs,
spilling graphs to disk and restoring them when they are needed is desirable. (David Goldschmidt
wrote the graph package when he was a student at Rensselaer Polytechnic Institute. See
Resources
). The package was written before the Collections framework existed; it doesn't implement
java.io.Serializable, and even the objects like Vertices that could be beans don't use standard get
and set names for properties. In short, it's a great real-world example to see if the new
persistence mechanism can save and restore graphs without any modifications to the source code of
the graph package.
Rather than fill the space here with the Graph, Vertex, and DirectedEdge APIs, be assured that
they are complex inheritance hierarchies with many data fields and methods. A graph object has a
lot of state information that is modified during various algorithms, but we don't care about that.
All we want to persist is the connection and name data for each vertex and edge.
Listing 7
demonstrates how you can build a three-node graph with three edges and then save and restore
it. We need to write three custom persistence delegates, one each for the Vertex, DirectedEdge, and
Graph classes. The Vertex and DirectedEdge delegates have constructors that take arguments that we
can use to instantiate and initialize the objects simultaneously. For these two we can use the
Expression form of Statement that returns a value. In this case, the method name argument is new,
which the XMLEncoder recognizes as calling for a constructor on the object, not a normal method.
The value returned is the constructed and initialized object.
The Graph delegate extends DefaultPersistenceDelegate to take advantage of the Graph default
constructor. Then we provide several Statement objects to the encoder to complete the
initialization. The Graph class provides public methods to get the vertices and edges as the old
style Enumeration, and it provides two public add methods, one each for vertices and edges. Running
the program verifies that it works, but the Graph built-in printSummary method also shows an
internal private unique node ID number. All we care about is the connectivity and names in this
application, but this points up something to keep in mind: restored objects are not identical to
the originals in every way. You must decide what should be saved and restored, and manage the items
that are important for your particular application.
mygraph:
# of vertices: 3
# of directed edges: 3
Vertices and incident edges:
node1: 1 (node2).
node2: 2 (node3).
node3: 3 (node1).
mygraph:
# of vertices: 3
# of directed edges: 3
Vertices and incident edges:
node1: 7 (node2).
node2: 8 (node3).
node3: 9 (node1).
GUIs can be complex, and they sometimes require the most effort to save and restore properly.
Using the original Java object-serialization mechanism is nearly useless because it often results
in megabyte-sized binary files when saving GUI data. It saves too much information from the entire
class hierarchy. Many real-world applications need to save part of a GUI but not the whole thing.
An example would be a GUI used as a menu in a fast food cash register. The menu—and thus the GUI—m
ight be changed frequently not by the original GUI software developer but by the store manager
using a simple interactive menu designer.
Three Strategies
Moving complete GUI designs between development tools seems best served by using the Java
source code rather than an intermediate form like XML. There are two main persistence issues for
saving GUIs: event handlers and default property values such as layout managers. The persistence
mechanism can access only public methods to restore an object. Therefore, inner classes or
nonpublic classes, often used by GUI IDE tools, for event handlers cannot be saved and restored.
There are at least three strategies to use for event handlers: use a public ActionBean that
implements an ActionListener approach; use the new JDK 1.4 java.beans.EventHandler.create(..)
method to make a dynamic proxy; and don't save event handlers, but instead set them after loading
the JPanel by iterating over the JPanel's components. For large real-world designs, the latter may
be the best choice.
Listing 8
implements an ultrasimple fast food menu GUI with a JPanel holding food order buttons. This
small example demonstrates all three of the event handler strategies. The ActionBean is:
package xmlpersist;import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;public class ActionBean
implements ActionListener {
public void actionPerformed
(ActionEvent e) {
System.out.println(
"button pressed , cmd = "
+ e.getActionCommand() );
}
}
The dynamic proxy method needs a target object. A "thing" object is the target; it will be
instantiated when the panel is restored because it is present in the XML. You shouldn't use the
frame itself—that is, "this" as the target—or it would make a new frame every time you restored a
panel:
package xmlpersist;public class Thing {
private static int thingCount=0;
public Thing() {thingCount++;} public void orderSomething(
String what) {
System.out.println(
"thing #"
+ thingCount +
" orderSomething = "
+ what);
}
}
You can go to a commercial implementation of a Java Swing fast food menu system (see
Resources
). Imagine that another menu designer GUI lets the store manager design and save various
JPanels full of buttons when food items or drinks change from day to day (see
Figure 1
). If the designer application can save them in XML in a database for use after a specified
date, and the menu GUI can read them in from a database given the current date, it would be a good
solution. For this test, just edit the XML file and change the button labels. Here is a sample
output:
button pressed , cmd = Hamburger
thing #1 orderSomething = Chicken// edit XML button labels
// to Ham and Chick
// and reload the panelthing #3 orderSomething = Chick
button pressed , cmd = Fish
button pressed , cmd = Ham
button pressed , cmd = Fish
Note that the Fish button on the restored panel is now active because we set the action listener
on it after loading the panel. It was not set in the original source code. The saved panel uses
FlowLayout with a left alignment.
Listing 9
shows the resulting XML. The FlowLayout component itself is not saved in the XML because it
happens to be the default layout manager for a JPanel. No special persistence delegates were
needed, and there were no save and restore problems for this GUI example. However, GUIs in general
are complex, and there will be some that don't work. In particular GUI components that use inner
classes properties that are transient in nature such as the visible property—and issues related to
flashing windows as the persistence mechanism clones objects to determine default properties—have
led to complex built-in persistence delegates for the GUI components.
This technology is still new; some of the built-in persistence delegates may have bugs.
Practical advice for saving GUIs would be to keep it simple, look at the XML for clues, and beware
of default component values that will not appear in the XML archive.
Resources
java.beans Class XMLEncoder
Graph Base
by David Goldschmidt, Rensselaer Polytechnic Institute
Exit41 Point-of-Sale Software Solutions
java.sun.com's
Java Technology Forums
java.sun.com's
Java 2 Platform, Standard Edition (J2SE)
page where you can access four Swing Connection archive articles (from Nov 1999 to Jan of
2002) on long-term persistence using XML by Philip Milne
Listing 1. This code provides a simple example of serializing the Stuff bean to
XML and retrieving it.
package xmlpersist;
import java.beans.*;
import java.io.*;public class Simple {
public static void main(String args ){
try {
String filename "out.txt";
XMLEncoder encoder =
new XMLEncoder(new FileOutputStream(filename));
Stuff stuff = new Stuff();
stuff.setK(3);
stuff.setS("goodbye");
encoder.writeObject(stuff);
encoder.close(); XMLDecoder decoder = new XMLDecoder(
new FileInputStream(filename));
Stuff x = (Stuff)decoder.readObject();
System.out.println(
"k=" + x.getK() + " s=" + x.getS());
}catch (FileNotFoundException e){
System.out.println("not found");
}
}
}
Listing 2. The ReadStuffBetter program uses an error listener on the XMLDecoder.
<pre>package xmlpersist;
import java.io.*;
import java.beans.*;// catches the internal xml exception messages
public class ReadStuffBetter {
public static void main(String[] args) {
try {
String file = "xmlout.txt";
ExceptionListener listener =
new WarningListener();
XMLDecoder decoder =
new XMLDecoder(new FileInputStream(
file),null,listener);
Stuff x = (Stuff)decoder.readObject();
System.out.println("Stuff k=" + x.getK() +
" s=" + x.getS());
} catch (FileNotFoundException fex) {
System.out.println("file not found" );
}
}
}
Listing 3. The same item that is stored twice in a HashMap is saved only once.
<pre>package xmlpersist;
import java.beans.*;
import java.io.*;
import java.util.*;public class LessSimple { // save Collection
public static void main(String[] args) {
HashMap map = new HashMap();
try {
String filename = "xmlout.txt";
XMLEncoder encoder =
new XMLEncoder(new FileOutputStream(
filename));
Stuff stuff = new Stuff();
stuff.setK(3);
stuff.setS("goodbye");
map.put("item1",stuff);
map.put("item2",stuff);
encoder.writeObject(map);
encoder.close(); XMLDecoder decoder =
new XMLDecoder(new FileInputStream(
filename));
HashMap x = (HashMap)decoder.readObject();
Stuff item1 = (Stuff)x.get("item1");
System.out.println("Retrieved Stuff k="
+ item1.getK() + " s=" + item1.getS());
} catch (java.io.FileNotFoundException fex) {
System.out.println("file not found exception"
+ fex.getMessage() );
}
}
}
Listing 4. In addition to an example of using ByteArrayOutputStream(), this code
is useful for writing XML to a varchar or text field in a database.
<pre>package xmlpersist;
import java.beans.*;
import java.io.*;
import java.util.*;public class StringWrite {
public static void main(String[] args) {
ByteArrayOutputStream os =
new ByteArrayOutputStream();
XMLEncoder encoder = new XMLEncoder(os);
Stuff stuff = new Stuff();
stuff.setK(3);
stuff.setS("goodbye");
encoder.writeObject(stuff);
encoder.close(); System.out.println(os.toString());
byte[] buf = os.toByteArray();
InputStream is = new ByteArrayInputStream(buf);
XMLDecoder decoder = new XMLDecoder( is );
Stuff x = (Stuff)decoder.readObject();
System.out.println("k=" + x.getK() + " s=" +
x.getS());
}
}
Listing 5. This StuffBeanInfo class was generated by Borland's JBuilder to provide
one way to specify nonstandard get and set methods.
<pre>package xmlpersist;
import java.beans.*;public class xStuffBeanInfo extends SimpleBeanInfo {
private Class beanClass = Stuff.class; public xStuffBeanInfo() {
}
public PropertyDescriptor[]
getPropertyDescriptors() {
try {
PropertyDescriptor _k = new PropertyDescriptor(
"k", beanClass, "getK", "setK");
PropertyDescriptor _s = new PropertyDescriptor(
"s", beanClass, "getS", "setS");
PropertyDescriptor _d = new PropertyDescriptor(
"d", beanClass, "what", "force");
_d.setDisplayName("d");
_d.setShortDescription("d");
PropertyDescriptor[] pds =
new PropertyDescriptor[]
{ _k, _s, _d};
return pds;
}
catch(IntrospectionException ex) {
ex.printStackTrace();
return null;
}
}
}
Listing 6. This custom persistence delegate for Stuff provides an alternative to
using the StuffBeanInfo class (see
Listing 5
).
<pre>package xmlpersist;
import java.beans.*;public class StuffPersistenceDelegate
extends DefaultPersistenceDelegate { protected void initialize(
Class type, Object oldInstance,
Object newInstance, Encoder out) {
//default bean work
super.initialize(
type, oldInstance, newInstance, out); Stuff oldStuff = (Stuff) oldInstance;
double arg = oldStuff.what();
// not the newInstance below!
Statement stm = new Statement(oldInstance,
// method that will be invoked on newInstance
// at decode time
"force",
// array of args to that method
new Object[] { new Double( arg )});
out.writeStatement( stm ); // registers stm with
// the encoder object
}
}
Listing 7. This code saves and restores a three-node graph using custom
persistence delegates.
<pre>package xmlpersist;
import rpi.goldsd.graph.*;
import java.beans.*;
import java.io.*;
import java.util.*;public class GraphTest {
public static void main(String[] args) {
boolean testVertex = false;
boolean testEdge = false; Graph g = new Graph("mygraph");
Vertex a,b,c;
g.add(a=new Vertex("node1"));
g.add(b=new Vertex("node2"));
g.add(c=new Vertex("node3"));
g.add(new DirectedEdge(a,b));
g.add(new DirectedEdge(b,c));
g.add(new DirectedEdge(c,a));
g.printSummary();
try {
String filename = "xmlout.txt";
XMLEncoder encoder = new XMLEncoder(
new FileOutputStream(filename));
encoder.setPersistenceDelegate(
Vertex.class,new PersistenceDelegate() {
protected Expression instantiate(
Object oldInstance,Encoder out) {
return new Expression(oldInstance,
oldInstance.getClass(),"new",
new Object[]{ oldInstance.toString() });
}});
encoder.setPersistenceDelegate(
DirectedEdge.class,
new PersistenceDelegate() {
protected Expression instantiate(
Object oldInstance,Encoder out) {
return new Expression(oldInstance,
oldInstance.getClass(),"new",new Object[]{
((DirectedEdge)oldInstance).startVertex(),
((DirectedEdge)oldInstance).endVertex()
});
}});
encoder.setPersistenceDelegate(Graph.class,
new GraphPersistenceDelegate()); if ( testVertex ) {
Vertex z = new Vertex("waldo"); encoder.writeObject(z);
encoder.close(); XMLDecoder decoder = new XMLDecoder(
new FileInputStream(filename));
Vertex x = (Vertex) decoder.readObject();
System.out.println("retrieved vertex info = "
+ x.toString());
}
else if ( testEdge) {
DirectedEdge z = new DirectedEdge(a,b);
System.out.println(
"original directed edge info = " +
z.toString()); encoder.writeObject(z);
encoder.close(); XMLDecoder decoder = new XMLDecoder(
new FileInputStream(filename));
DirectedEdge x = (DirectedEdge)
decoder.readObject();
System.out.println("retrieved edge info = " +
x.startVertex().toString() + "," +
x.endVertex().toString());
}
else { // test graph, save and restore
encoder.writeObject(g);
encoder.close();
XMLDecoder decoder = new XMLDecoder(
new FileInputStream(filename));
Graph p = (Graph) decoder.readObject();
p.printSummary(); XMLEncoder encoder2 = new XMLEncoder(
new FileOutputStream("test.txt"));
// note that the delegates we set before are
// now part of the metadata and don't have to
// be set again for this new encoder!
encoder2.writeObject(g);
encoder2.close(); }
}catch(IOException e){ System.err.println(
"IOException " + e.getMessage());}
}
}class GraphPersistenceDelegate
extends DefaultPersistenceDelegate {
protected void initialize(Class type,
Object oldInstance,
Object newInstance,
Encoder out) {
super.initialize(type, oldInstance,
newInstance, out); Graph oldGraph =(Graph)oldInstance;
Enumeration en = oldGraph.vertices();
while ( en.hasMoreElements() ) {
out.writeStatement(
new Statement(oldInstance,
"add",
new Object[] {
(Vertex)en.nextElement() }) ); }
en = oldGraph.edges();
while ( en.hasMoreElements() ) {
out.writeStatement(
new Statement(oldInstance,
"add",
new Object[]{ (Edge)en.nextElement() }) ); }
out.writeStatement(
new Statement(oldInstance,
"setName",
new Object[]{ oldGraph.name() }) ); }
}
Listing 8. In this simple fast food menu GUI implementation, there are three
methods for persisting event handlers.
<pre>package
xmlpersist;import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;import java.beans.*;
import java.io.*;public class Frame1 extends JFrame {
Thing thing = new Thing();
JPanel contentPane;
JPanel jPanel1 = new JPanel();
JPanel jPanel2 = new JPanel();
BorderLayout borderLayout1 = new BorderLayout();
FlowLayout flowLayout2 = new FlowLayout(
FlowLayout.LEFT);
JButton jButton1 = new JButton();
JButton jButton2 = new JButton();
Border border1;
JButton jButton3 = new JButton();
JButton jButton4 = new JButton();
JButton jButton5 = new JButton();
JButton jButton6 = new JButton();//Construct the frame
public Frame1() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
//Component initialization
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
border1 = BorderFactory.createLineBorder(
Color.blue,1);
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(400, 300));
this.setTitle("XML persist test"); jPanel2.setLayout(flowLayout2);
jButton1.setText("SavePanel");
jButton1.addActionListener(
new Frame1_jButton_actionAdapter(this));
jButton2.setText("RestorePanel");
jButton2.addActionListener(
new Frame1_jButton2_actionAdapter(this));
jPanel2.setBorder(border1);
jButton3.setText("Hamburger");
// listener Method (1) for events
jButton3.addActionListener( new ActionBean() );
jButton4.setSelected(false);
jButton4.setText("Chicken"); // In EventHandler.create, don't use "this" for
// the 3rd argument which is the owner, if you
// do it makes a whole new Frame on loading the
// panel. The same 'thing' can be the target
// many times but it is only saved and restored
// once.
jButton4.addActionListener(
(ActionListener)EventHandler.create(
// Method (2)
ActionListener.class,thing, "orderSomething",
"source.text"));
jButton6.addActionListener(
(ActionListener)EventHandler.create(
// Method (2)
ActionListener.class,thing, "orderSomething",
"source.text")); jButton5.setText("Fish");
jButton6.setText("Salad");
contentPane.add(jPanel1, BorderLayout.NORTH);
jPanel1.add(jButton1, null);
jPanel1.add(jButton2, null);
contentPane.add(jPanel2, BorderLayout.CENTER);
jPanel2.add(jButton3, null);
jPanel2.add(jButton4, null);
jPanel2.add(jButton6, null);
jPanel2.add(jButton5, null);
}
//Overridden so we can exit when window is closed
protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
} void jButton1_actionPerformed(
ActionEvent e) { // Save
try {
XMLEncoder encoder = new XMLEncoder(
new FileOutputStream("lunchPanel.xml"));
encoder.writeObject(jPanel2);
encoder.close();
} catch ( FileNotFoundException ex)
{ ex.printStackTrace();}
} void jButton2_actionPerformed(
ActionEvent e) { // Restore
try {
XMLDecoder decoder = new XMLDecoder(
new FileInputStream("lunchPanel.xml"));
JPanel panel = (JPanel)decoder.readObject();
this.contentPane.remove(jPanel2);
jPanel2 = panel; // in case we hit the save
// button again
this.getContentPane().add(panel);
Component comps[] = panel.getComponents();
// Method(3), Add listener after loading panel
for ( int i = 0 ; i < comps.length ; i++ ) {
if ( comps[i] instanceof JButton ) {
JButton b = (JButton) comps[i];
ActionListener listeners[] =
b.getActionListeners();
if ( listeners.length == 0 )
b.addActionListener(new ActionBean() );
}
}
this.validate();
} catch ( FileNotFoundException ex)
{ ex.printStackTrace();}
}
}class Frame1_jButton_actionAdapter
implements java.awt.event.ActionListener {
Frame1 adaptee; Frame1_jButton_actionAdapter(Frame1 adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jButton1_actionPerformed(e);
}
}class Frame1_jButton2_actionAdapter
implements java.awt.event.ActionListener {
Frame1 adaptee; Frame1_jButton2_actionAdapter(Frame1 adaptee) {
this.adaptee = adaptee;
}
public void actionPerformed(ActionEvent e) {
adaptee.jButton2_actionPerformed(e);
} public static void main(String[] args) {
Frame1 theFrame = new Frame1();
theFrame.setVisible(true);
}
}
Listing 9. Note the event handlers and lack of any default values in this XML
result.
<pre><?xml version="1.0"
encoding="UTF-8"?>
<java version="1.4.1" class=
"java.beans.XMLDecoder">
<object class="javax.swing.JPanel">
<void method="add">
<object class="javax.swing.JButton">
<string>Ham</string>
<void method="addActionListener">
<object class="xmlpersist.ActionBean"/>
</void>
</object>
</void>
<void method="add">
<object class="javax.swing.JButton">
<string>Chick</string>
<void method="addActionListener">
<object class="java.beans.EventHandler"
method="create">
<class>java.awt.event.ActionListener
</class>
<object id="Thing0" class=
"xmlpersist.Thing"/>
<string>orderSomething</string>
<string>source.text</string>
</object>
</void>
</object>
</void>
<void method="add">
<object class="javax.swing.JButton">
<string>Salad</string>
<void method="addActionListener">
<object class="java.beans.EventHandler"
method="create">
<class>java.awt.event.ActionListener
</class>
<object idref="Thing0"/>
<string>orderSomething</string>
<string>source.text</string>
</object>
</void>
</object>
</void>
<void method="add">
<object class="javax.swing.JButton">
<string>Fish</string>
</object>
</void>
<void property="border">
<object class="javax.swing.border.LineBorder">
<object class="java.awt.Color">
<int>0</int>
<int>0</int>
<int>255</int>
<int>255</int>
</object>
<int>1</int>
</object>
</void>
<void property="layout">
<void property="alignment">
<int>0</int>
</void>
</void>
</object>
</java>
|