Download Serialize Java Data Objects to XML

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
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 threenode 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—might 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 panel
thing #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.
About the Author
Charlie Havener is an independent software developer specializing in Java applications
ranging from database-driven Web sites to GUIs to custom compilers. Recently Charlie
was involved in a project to develop a Verilog compiler for a digital circuit simulator
accelerator using the JavaCC parsing tool. Reach Charlie at [email protected].
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 arti
Serializing the Stuff Bean
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");
}
}
}
ReadStuffBetter
Listing 2. The ReadStuffBetter program uses an error listener on the XMLDecoder.
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" );
}
}
}
Saving a Java Collection
Listing 3. The same item that is stored twice in a HashMap is saved only once.
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() );
}
}
}
Write to a String Buffer
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.
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());
}
}
StuffBeanInfo Class
Listing 5. This StuffBeanInfo class was generated by Borland's JBuilder to provide one
way to specify nonstandard get and set methods.
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;
}
}
}
Custom Persistence Delegate for Stuff
Listing 6. This custom persistence delegate for Stuff provides an alternative to using the
StuffBeanInfo class (see Listing 5).
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
}
}
Build a Three-Node Graph
Listing 7. This code saves and restores a three-node graph using custom persistence
delegates.
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() }) );
}
}
Saving GUI JPanels with Event Handlers
Listing 8. In this simple fast food menu GUI implementation, there are three methods for
persisting event handlers.
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);
}
}
XML from Saving the JPanel with Buttons
Listing 9. Note the event handlers and lack of any default values in this XML result.
<?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>
Figure 1. Fast Food Menu Changes
Editing the XML and changing the button labels tests a solution in which a menu designer GUI allows a user to design and
save various JPanels full of buttons for day-to-day food item changes. The 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.