<< Back - Sheepdog Software

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>

©2006 Sheepdog Solutions Inc. (http://www.sheepdogsoftware.net) - Innovative Solutions Providers