Friday, 11 March 2011

XMLEncoder and XMLDecoder

We will look at two approaches to representing data from Java programs in XML format. One approach is to design a custom XML language for the specific data structures that you want to represent. We will consider this approach in the next subsection. First, we'll look at an easy way to store data in XML files and to read those files back into a program. The technique uses the classes XMLEncoder and XMLDecoder. These classes are defined in the package java.beans. An XMLEncoder can be used to write objects to an OutputStream in XML form. An XMLDecoder can be used to read the output of an XMLEncoder and reconstruct the objects that were written by it. XMLEncoder and XMLDecoder have much the same functionality as ObjectOutputStream and ObjectInputStream and are used in much the same way. In fact, you don't even have to know anything about XML to use them. However, you do need to know a little about Java beans.

XMLEncoder and XMLDecoder can't be used with arbitrary objects; they can only be used with beans. When an XMLEncoder writes an object, it uses the "get" methods of that object to find out what information needs to be saved. When an XMLDecoder reconstructs an object, it creates the object using the constructor with no parameters and it uses "set" methods to restore the object's state to the values that were saved by the XMLEncoder. (Some standard java classes are processed using additional techniques. For example, a different constructor might be used, and other methods might be used to inspect and restore the state.)

Suppose that we want to use XMLEncoder and XMLDecoder to create and read files in that program. Part of the data for a SimplePaint sketch is stored in objects of type CurveData, defined as:

private static class CurveData {
Color color; // The color of the curve.
boolean symmetric; // Are reflections also drawn?
ArrayList<Point> points; // The points on the curve.
}



To use such objects with XMLEncoder and XMLDecoder, we have to modify this class so that it follows the Java bean pattern. The class has to be public, and we need get and set methods for each instance variable. This gives:


public static class CurveData {
private Color color; // The color of the curve.
private boolean symmetric; // Are reflections also drawn?
private ArrayList<Point> points; // The points on the curve.
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public ArrayList<Point> getPoints() {
return points;
}
public void setPoints(ArrayList<Point> points) {
this.points = points;
}
public boolean isSymmetric() {
return symmetric;
}
public void setSymmetric(boolean symmetric) {
this.symmetric = symmetric;
}
}



I didn't really need to make the instance variables private, but bean properties are usually private and are accessed only through their get and set methods.

At this point, we might define another bean class, SketchData, to hold all the necessary data for representing the user's picture. If we did that, we could write the data to a file with a single output statement. In my program, however, I decided to write the data in several pieces.

An XMLEncoder can be constructed to write to any output stream. The output stream is specified in the encoder's constructor. For example, to create an encoder for writing to a file:

XMLEncoder encoder; 
try {
FileOutputStream stream = new FileOutputStream(selectedFile);
encoder = new XMLEncoder( stream );
.
.


Once an encoder has been created, its writeObject() method is used to write objects, coded into XML form, to the stream. In the SimplePaint program, I save the background color, the number of curves in the picture, and the data for each curve. The curve data are stored in a list of type ArrayList<CurveData> named curves. So, a complete representation of the user's picture can be created with:

encoder.writeObject(getBackground());
encoder.writeObject(new Integer(curves.size()));
for (CurveData c : curves)
encoder.writeObject(c);
encoder.close();



When reading the data back into the program, an XMLDecoder is created to read from an input file stream. The objects are then read, using the decoder's readObject() method, in the same order in which they were written. Since the return type of readObject() is Object, the returned values must be type-cast to their correct type:

Color bgColor = (Color)decoder.readObject();
Integer curveCt = (Integer)decoder.readObject();
ArrayList<CurveData> newCurves = new ArrayList<CurveData>();
for (int i = 0; i < curveCt; i++) {
CurveData c = (CurveData)decoder.readObject();
newCurves.add(c);
}
decoder.close();
curves = newCurves; // Replace the program's data with data from the file.
setBackground(bgColor);
repaint();


The XML format used by XMLEncoder and XMLDecoder is more robust than the binary format used for object streams and is more appropriate for long-term storage of objects in files.

Full program

No comments:

Post a Comment