Serializing Objects and Data

XML Serialization Essentials explained that serialization is a useful way to implement component-to-component communication or application-to-application communication. This chapter guides you through the creation of a simple WebObjects application that shows how serialization, both binary-based and XML-based, can be implemented.

The chapter is divided in two sections:

Binary Serialization Example

This section guides you through the creation of a straightforward application that serializes and deserializes objects and primitive values into and from binary form using Java's binary-serialization facilities.

Creating the Serialization Project

Using Project Builder, create a WebObjects application project named Serialization. You don't need to add any frameworks to the project, such as the Java JDBC Adaptor framework. (You can look at the finalized project in projects/Serializing/Binary/Serialization.)

Serialization.pbprojSerialization.pbproj

Adding the BinarySerializer Class

The BinarySerializer class manages binary serialization and deserialization to and from files. To save yourself some typing, you can add the BinarySerializer.java file in projects/Serializing/Binary/Serialization to your project's Application Server target. Otherwise, follow the steps below.

  1. Select the Classes group in the Serializer project.

  2. Choose File > New File.

  3. In the New File pane of the Project Builder Assistant, select Java Class under WebObjects and click Next.

  4. In the New Java Class pane, name the class BinarySerializer and click Finish.

  5. Replace the template code in BinarySerializer.java with the code in BinarySerialization.java.

BinarySerializer.java provides two main functions: serialization and deserialization of objects and creation and disposal of ObjectOutputStream and ObjectInputStream objects.

The serializeObject and deserializeObject methods serialize and deserialize objects to and from a file. You can use the openStream and closeStream methods to serialize primitive-type values or individual objects. See Serializing Primitive-Type Values to an XML Document for an example.

Serializing an NSArray of Strings

Now that you have the BinarySerializer class as part of your project, you can use it to serialize objects.

The first step is to add three methods to the Application class: one that instantiates, populates, and serializes an NSArray of Strings; a second one that deserializes and displays the data; and a third one that invokes the other two. Listing 3-1 shows the methods.

Listing 3-1  The serializeArray, deserializeArray, and arraySerialization methods in Application.java

/**
 * Creates and serializes an NSArray of Strings.
 * @param identifier      identifies the target file, without a
 *                        path or an extension
 */
public void serializeArray(String identifier) {
    // Instantiate object to serialize.
    NSArray book_titles =  new NSArray(new Object[] {"The Chestry Oak", "A Tree for Peter", "The White Stag"});
 
    // Serialize the object.
    BinarySerializer.serializeObject(book_titles, identifier);
}
 
/**
 * Deserializes an NSArray and writes its contents to the console.
 * @param identifier      identifies the source file, without a
 *                        path or an extension
 */
public void deserializeArray(String identifier) {
    // Deserialize the data and assign it to an NSArray object.
    NSArray books = (NSArray)BinarySerializer.deserializeObject(identifier);
 
    // Display the contents of <code>books</code> on the console
    // (the Run pane in Project Builder).
    System.out.println("");
    System.out.println("** Deserialized NSArray **");
    System.out.println(books);
    System.out.println("");
}
 
/**
 * Invokes the <code>serializeArray</code> and
 * <code>deserializeArray</code> methods.
 */
public void arraySerialization() {
    String identifier = "BookTitles";
 
    // Serialize NSArray object.
    serializeArray(identifier);
 
    // Deserialize NSArray object.
    deserializeArray(identifier);
}

Finally, modify the Application class's constructor so that it looks like Listing 3-2.

Listing 3-2  The constructor in Application.java

/**
 * Creates an Application object. Invoked once during application startup.
 */
public Application() {
    super();
    System.out.println("Welcome to " + this.name() + "!");
 
    // Test serialization of an array.
    arraySerialization();
}

After you build and run the application, Project Builder's Run pane should look similar to Listing 3-1.

Figure 3-1  Project Builder's Run pane when running the array-serialization example
Project Builder's Run pane when running the array-serialization exampleProject Builder's Run pane when running the array-serialization example

If you open /tmp/BookTitles_data.binary (generated by the serialization process) in a text editor, you should see something similar to Listing 3-2.

Figure 3-2  BinaryTitles_data.binary file viewed through a text editor
BinaryTitles_data.binary file viewed through a text editor

This hardly qualifies as human-readable. XML Serialization Essentials shows you how to create files with serialized data that are easier for people to read and modify.

Serializing Primitive-Type Values

This section shows you how to serialize primitive-type values that are not encapsulated by objects. To accomplish this, you instantiate an ObjectOutputStream and invoke one or more of its write methods.

Add three methods to the Application class of the Serialization project: one that serializes values of type int, boolean, char and double; a second that deserializes the data; and a third one that calls the other two. Listing 3-3 gives you an example of such methods.

Listing 3-3  The serializePrimitives, deserializePrimitives, and primitiveSerialization methods in Application.java

/**
 * Serializes a set of primitive values.
 *
 * @param filename    identifies the target file, including its
 *                    path and extension
 *
 * @param an_int      value to serialize
 * @param a_boolean   value to serialize
 * @param a_char      value to serialize
 * @param a_double    value to serialize
 */
public void serializePrimitives(String filename, int an_int, boolean a_boolean, char a_char, double a_double) {
    try {
        // Open an output stream.
        ObjectOutputStream stream = BinarySerializer.openOutputStream(filename);
 
        // Write values.
        stream.writeInt(an_int);
        stream.writeBoolean(a_boolean);
        stream.writeChar(a_char);
        stream.writeDouble(a_double);
 
        // Close the stream.
        BinarySerializer.closeStream(filename);
    }
 
    catch (IOException e) {
        e.printStackTrace();
    }
}
 
/**
 * Deserializes a set of primitive values.
 *
 * @param filename   identifies the source file, including its
 *                   path and extension
 */
public void deserializePrimitives(String filename) {
    try {
        // Open an input stream.
        ObjectInputStream stream = BinarySerializer.openInputStream(filename);
 
        // Read values.
        int the_int = stream.readInt();
        boolean the_boolean = stream.readBoolean();
        char the_char = stream.readChar();
        double the_double = stream.readDouble();
 
        BinarySerializer.closeStream(filename);
 
        // Write values to console (Run pane in Project Builder).
        System.out.println("");
        System.out.println("** Deserialized primitives **");
        System.out.println("int: " + the_int);
        System.out.println("boolean: " + the_boolean);
        System.out.println("char: " + the_char);
        System.out.println("double: " + the_double);
        System.out.println("");
    }
 
    catch (IOException e) {
        e.printStackTrace();
    }
}
 
/**
 * Invokes the <code>serializePrimitives</code> and
 * <code>deserializePrimitives</code> methods.
 */
public void primitiveSerialization() {
    String filename = "/tmp/PrimitiveValues_data.binary";
 
    // Serialize primitive values.
    serializePrimitives(filename, 5, true, 'u', 3.14);
 
    // Deserialize primitive values.
    deserializePrimitives(filename);
}

You also need to add the following to the Application class:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

Finally, add a call to the primitiveSerialization method in the Application class's constructor:

// Test serialization of primitive values.
primitiveSerialization();

Build and run the application. Project Builder's Run pane should look similar to Listing 3-3.

Figure 3-3  Project Builder's Run pane when running the primitive-values serialization example
Project Builder's Run pane when running the primitive-values serialization exampleProject Builder's Run pane when running the primitive-values serialization example

Serializing Custom Objects

Now that you've mastered the art of serializing an instance of a WebObjects Serializable class and primitive-type values, you're ready to tackle the serialization of custom objects. For this, you create a Movie class that includes writeObject and readObject methods.

Add a class named Movie to the Serialization project and assign it to the Application Server target. Modify Movie.java so that it looks like Listing 3-4. (Alternatively, you can add the Movie.java file in projects/Serializing/Binary/Serialization to your project.)

Listing 3-4  Movie.java using binary serialization

import com.webobjects.appserver.*;
import com.webobjects.foundation.*;
import com.webobjects.foundation.xml.*;
import com.webobjects.eocontrol.*;
 
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.sql.Timestamp;
 
/**
 * Manages movie information.
 */
public class Movie extends Object implements Serializable {
    private String title;
    private String studio;
    private NSTimestamp releaseDate;
 
    /**
     * Creates a Movie object.
     *
     * @param name          movie title
     * @param studio        studio that released the movie
     * @param release_date  date the movie was released
     */
    Movie(String title, String studio, NSTimestamp releaseDate) {
        super();
 
        setTitle(title);
        setStudio(studio);
        setReleaseDate(releaseDate);
    }
 
    /**
     * Gets this movie's title.
     *
     * @return movie title.
     */
    public String title() {
        return this.title;
    }
 
    /**
     * Sets this movie's title.
     *
     * @param value            movie's title
     */
    public void setTitle(String value) {
        this.title = value;
    }
 
    /**
     * Gets this movie's studio.
     *
     * @return movie studio.
     */
    public String studio() {
        return this.studio;
    }
 
    /**
     * Sets this movie's studio.
     *
     * @param value           studio's name
     */
    public void setStudio(String value) {
        this.studio = value;
    }
 
    /**
     * Gets this movie's release date.
     *
     * @return movie release date.
     */
    public NSTimestamp releaseDate() {
        return this.releaseDate;
    }
 
    /**
     * Sets this movie's release date.
     *
     * @param value          release date
     */
    public void setReleaseDate(NSTimestamp value) {
        this.releaseDate = value;
    }
 
    /**
     * Gets the string representation of this movie.
     *
     * @return string representing this movie.
     */
    public String toString() {
        return "(Movie: (Title: " + title() + "), (Studio: " + studio() + "), (Release Date: " + releaseDate().toString() +  "))";
    }
 
    /**
     * Serializes this object.
     *
     * @param stream   object stream to serialize this object to
     */
    private void writeObject(ObjectOutputStream stream) throws IOException {
        // Serialize the object's instance members.
        // (This is where you put special encoding logic,
        // such as the one used to encode the releaseDate field.)
        stream.writeObject(title());
        stream.writeObject(studio());
        stream.writeObject(releaseDate().toString());
    }
 
    /**
     * Deserializes this object.
     *
     * @param stream   object stream from which the serialized data
     *                 is obtained
     */
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        // Deserializes the data a put it in the object's instance members.
        // (This is where you would put special de-encoding logic
        // such as the one used to decode the releaseDate field.)
        setTitle((String)stream.readObject());
        setStudio((String)stream.readObject());
        setReleaseDate(_timestampFromString((String)stream.readObject()));
    }
 
    /**
     * Converts a string into an NSTimestamp.
     *
     * @param timestampAsString     string to convert
     *
     * @return NSTimestamp object represented by timestampAsString.
     */
    private NSTimestamp _timestampFromString(String timestampAsString) {
        NSTimestampFormatter formatter = new NSTimestampFormatter();
        java.text.ParsePosition pp = new java.text.ParsePosition(0);
 
        return (NSTimestamp)formatter.parseObject(timestampAsString, pp);
    }
}

Now add the methods in Listing 3-5 to Application.java.

Listing 3-5  The serializeMovie, deserializeMovie, and movieSerialization methods in Application.java

/**
 * Serializes a Movie object.
 *
 * @param identifier   identifies the target file, without a
 *                     a path or an extension
 */
public void serializeMovie(String identifier) {
    // Set the local time zone.
    NSTimeZone timeZone = NSTimeZone.timeZoneWithName("America/Los_Angeles", true);
 
    Movie movie = new Movie("Alien", "20th Century Fox", new NSTimestamp(1979, 10, 25, 0, 0, 0, timeZone));
 
    BinarySerializer.serializeObject(movie, identifier);
}
 
/**
 * Deserializes Movie data into an object.
 *
 * @param identifier   identifies the source file, without a
 *                     a path or an extension
 */
public void deserializeMovie(String identifier) {
    Movie movie = (Movie)BinarySerializer.deserializeObject(identifier);
 
    System.out.println("");
    System.out.println("** Deserialized Movie object **");
    System.out.println(movie.toString());
    System.out.println("");
}
 
/**
 * Invokes the <code>movieSerialization</code> and
 * <code>movieDeserialization</code> methods.
 */
public void movieSerialization() {
    String identifier = "Movie";
 
    serializeMovie(identifier);
    deserializeMovie(identifier);
}

Finally, modify the Application class's constructor by adding a call to the movieSerialization method:

// Serialize a Movie object.
movieSerialization();

After building and running the application, you should see something similar to Listing 3-4.

Figure 3-4  Project Builder's Run pane when running the Movie-object serialization example
Project Builder's Run pane when running the Movie-object serialization exampleProject Builder's Run pane when running the Movie-object serialization example

The remainder of the section shows you how to serialize an NSMutableArray of Movies and deserialize the data into an NSArray.

Add the methods shown in Listing 3-6 to Application.java.

Listing 3-6  The serializeMovieArray, deserializeMovieArray, and movieArraySerialization methods of Application.java

/**
 * Serializes a Movie array.
 *
 * @param identifier   identifies the target file, without a
 *                     a path or an extension
 */
public void serializeMovieArray(String identifier) {
    // Set the local time zone.
    NSTimeZone timeZone = NSTimeZone.timeZoneWithName("America/Los_Angeles", true);
 
    // Initialize the array.
    NSMutableArray movies = new NSMutableArray();
    movies.addObject(new Movie("Alien", "20th Century Fox", new NSTimestamp(1979, 10, 25, 0, 0, 0, timeZone)));
    movies.addObject(new Movie("Blade Runner", "Warner Brothers", new NSTimestamp(1982, 1, 3, 0, 0, 0, timeZone)));
    movies.addObject(new Movie("Star Wars", "20th Century Fox", new NSTimestamp(1977, 12, 29, 0, 0, 0, timeZone)));
 
    // Serialize the array.
    BinarySerializer.serializeObject(movies, identifier);
}
 
/**
 * Deserializes Movie data into an NSArray.
 *
 * @param identifier   identifies the source file, without a
 *                     a path or an extension
 */
public void deserializeMovieArray(String identifier) {
    // Create an empty array.
    NSArray movies = new NSArray();
 
    // Deserialize data into movies.
    movies = (NSArray)BinarySerializer.deserializeObject(identifier);
 
    System.out.println("");
    System.out.println("** Deserialized Movie array **");
    System.out.println(movies.toString());
    System.out.println("");
}
 
/**
 * Invokes the <code>movieArraySerialization</code> and
 * <code>movieArrayDeserialization</code> methods.
 */
public void movieArraySerialization() {
    String identifier = "Movies";
 
    serializeMovieArray(identifier);
    deserializeMovieArray(identifier);
}

Add a call to the movieArraySerialization method in the Application class's constructor and build and run the application. The Project Builder Run pane should look like Listing 3-5.

Figure 3-5  Project Builder's Run pane when running the Movie-array serialization example
Project Builder's Run pane when running the Movie-array serialization exampleProject Builder's Run pane when running the Movie-array serialization example

XML Serialization Example

As you learned in XML Serialization Essentials WebObjects XML serialization works essentially the same way Java binary serialization does. This section shows how to modify the Serialization project so that it uses a new class, XMLSerializer, to serialize and deserialize objects and data.

If you did not create the Serialization project in Binary Serialization Example, you can get it from this document's example projects in projects/Serializing/Binary.

Adding the XMLSerializer Class

Start by adding a new class called XMLSerializer.java to the project and assigning it to the Application Server target. Then modify the class so that it looks like XMLSerializer.java. (Alternatively, you can add the XMLSerializer.java file in projects/Serializing/XML/Serialization to your project.)

Serializing an NSArray of Strings to an XML Document

Now that the Serialization project contains the utility class XMLSerializer, modify the serializeArray and deserializeArray methods in Application.java so that they look like Listing 3-7. (All you need to do is change occurrences of BinarySerializer to XMLSerializer in the lines numbered 1 and 2.)

Listing 3-7  The serializeArray and deserializeArray methods in Application.java using XML serialization

/**
 * Creates and serializes an NSArray of Strings.
 * @param identifier          identifies the target file, without a
 *                            path or an extension
 */
public void serializeArray(String identifier) {
    // Instantiate object to serialize.
    NSArray book_titles =  new NSArray(new Object[] {"The Chestry Oak", "A Tree for Peter", "The White Stag"});
 
    // Serialize the object.
    XMLSerializer.serializeObject(book_titles, identifier);
}
 
/**
 * Deserializes an NSArray and writes its contents to the console.
 * @param identifier          identifies the source file, without a
 *                            path or an extension
 */
public void deserializeArray(String identifier) {
    // Deserialize the data and assign it to an NSArray object.
    NSArray books = (NSArray)XMLSerializer.deserializeObject(identifier);
 
    // Display the contents of <code>books</code> on the console
    // (the Run pane in Project Buider).
    System.out.println("");
    System.out.println("** Deserialized NSArray **");
    System.out.println(books);
    System.out.println("");
}

After building and running the application, you can find the BookTitles_data.xml file (shown in Listing 3-8) in /tmp.

Listing 3-8  BookTitles_data.xml (serialized array of Strings)

<?xml version="1.0" encoding="UTF-8"?>
<content xmlns="http://www.apple.com/webobjects/XMLSerialization"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xsi:schemaLocation="http://www.apple.com/webobjects/XMLSerialization
http://www.apple.com/webobjects/5.2/schemas/woxml.xsd">
    <object id="2">
        <class flag="3" id="0" name="com.webobjects.foundation.NSArray" suid="-
3789592578296478260">
            <field name="objects" type="java.lang.Object[]"/>
        </class>
        <array field="objects" id="4" ignoreEDB="1" length="3" type="java.lang.Object[]">
            <string id="5">The Chestry Oak</string>
            <string id="6">A Tree for Peter</string>
            <string id="7">The White Stag</string>
        </array>
    </object>
</content>

As you can see, the serialized version of the NSArray object is easier to read than BookTitles_data.binary, created in Serializing an NSArray of Strings. Listing 3-6 graphically depicts BookTitles_data.xml. However, the document is somewhat verbose. Transforming XML Documents shows you how to transform XML streams generated by NSXMLOutputStream into streamlined XML documents.

Figure 3-6  The element hierarchy of the BoolTitles_data.xml document
The element hierarchy of the BoolTitles_data.xml document

Serializing Primitive-Type Values to an XML Document

By now you've probably noticed how easy it is to serialize objects into XML documents. This section shows you how to serialize primitive-type values.

First, add the following code line to Application.java:

import com.webobjects.foundation.xml.*;

Now, modify the serializePrimitives and deserializePrimitives methods so that they match Listing 3-9 (You need to modify only the five numbered lines.)

Listing 3-9  The serializePrimitives, deserializePrimitives and primitiveSerialization methods in Application.java using XML serialization

/**
 * Serializes a set of primitive values.
 *
 * @param filename     identifies the target file, including its
 *                     path and extension
 * @param an_int       value to serialize
 * @param a_boolean    value to serialize
 * @param a_char       value to serialize
 * @param a_double     value to serialize
 */
public void serializePrimitives(String filename, int an_int, boolean a_boolean,
char a_char, double a_double) {
    try {
        // Open an output stream.
        NSXMLOutputStream stream = XMLSerializer.openOutputStream(filename, null);
 
        // Write values.
        stream.writeInt(an_int);
        stream.writeBoolean(a_boolean);
        stream.writeChar(a_char);
        stream.writeDouble(a_double);
 
        // Close the stream.
        XMLSerializer.closeStream(filename);
    }
 
    catch (IOException e) {
        e.printStackTrace();
    }
 }
 
/**
 * Deserializes a set of primitive values.
 *
 * @param filename     identifies the source file, including
 *                     its path and extension
 */
public void deserializePrimitives(String filename) {
    try {
        // Open an input stream.
        NSXMLInputStream stream = XMLSerializer.openInputStream(filename);
 
        // Read values.
        int the_int = stream.readInt();
        boolean the_boolean = stream.readBoolean();
        char the_char = stream.readChar();
        double the_double = stream.readDouble();
 
        XMLSerializer.closeStream(filename);
 
        // Write values to console (Run pane in Project Builder).
        System.out.println("");
        System.out.println("** Deserialized primitives **");
        System.out.println("int: " + the_int);
        System.out.println("boolean: " + the_boolean);
        System.out.println("char: " + the_char);
        System.out.println("double: " + the_double);
        System.out.println("");
    }
 
    catch (IOException e) {
        e.printStackTrace();
    }
 }
 
/**
 * Invokes the <code>serializePrimitives</code> and
 * <code>deserializePrimitives</code> methods.
 */
public void primitiveSerialization() {
    String filename = "/tmp/PrimitiveValues_data.xml";
 
    // Serialize primitive values.
    serializePrimitives(filename, 5, true, 'u', 3.14);
 
    // Deserialize primitive values.
    deserializePrimitives(filename);
}

Notice the signature of the openOutputStream method of the XMLSerializer class (Listing B-2):

openOutputStream(String filename, String transformation) throws IOException;

The invoking code specifies the type of transformation to perform on the serialized data through the transformation parameter. In this case, however, serializePrimitives does not perform a transformation; therefore, it invokes openOutputStream with transformation set to null. See XML Transformation for more information on transforming XML documents.

After building and running the application, your /tmp directory should contain the PrimitiveValues_data.xml. file. Listing 3-10 shows its contents.

Listing 3-10  The PrimitiveValues_data.xml file

<?xml version="1.0" encoding="UTF-8"?>
<content xmlns="http://www.apple.com/webobjects/XMLSerialization" xmlns:xsi="http://www.w3.org/2001/
XMLSchema-instance" xsi:schemaLocation="http://www.apple.com/webobjects/
XMLSerialization
http://www.apple.com/webobjects/5.2/schemas/woxml.xsd">
    <int>5</int>
    <boolean>true</boolean>
    <ch>u</ch>
    <double>3.14</double>
</content>

Serializing With Keys

Keys can help a great deal in describing what a data element represents. Adding keys to values as they are serialized is a simple process. Modify serializePrimitives so that it looks like Listing 3-11 (change the numbered code lines), and build and run the application.

Listing 3-11  The serializePrimitives method of Application.java using keys to identify elements in XML document

public void serializePrimitives(String filename, int an_int, boolean a_boolean, char a_char, double a_double) {
    try {
        // Open an output stream.
        NSXMLOutputStream stream = XMLSerializer.openOutputStream(
                                                        filename, null);
 
        // Write values.
        stream.writeInt(an_int, "my_integer");
        stream.writeBoolean(a_boolean, "my_boolean");
        stream.writeChar(a_char, "my_char");
        stream.writeDouble(a_double, "my_double");
 
        // Close the stream.
        XMLSerializer.closeStream(filename);
    }
 
    catch (IOException e) {
        e.printStackTrace();
    }
}

Now, after building and running the project, the PrimitiveValues_data.xml file looks like Listing 3-12.

Listing 3-12  The PrimitiveValues_data.xml file with keys identifying each element

<?xml version="1.0" encoding="UTF-8"?>
<content xmlns="http://www.apple.com/webobjects/XMLSerialization"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.apple.com/webobjects/XMLSerialization
http://www.apple.com/webobjects/5.2/schemas/woxml.xsd">
    <int key="my_integer">5</int>
    <boolean key="my_boolean">true</boolean>
    <ch key="my_char">u</ch>
    <double key="my_double">3.14</double>
</content>

The code in Transforming Primitive-Type Values Using Keys takes advantage of the key attribute on each element of the content element to transform the source XML document in Listing 3-12 to one that uses the values of those keys as the tag names of the elements that contain the data values in the target document, shown in Listing 5-6.

Serializing Custom Objects to an XML Document

You can take advantage of keys in custom Serializable objects by invoking the writeObject(Object, String) method of NSXMLOutputStream in the writeObject method of your custom class. To accomplish this, however, you have to cast the ObjectOutputStream argument to NSXMLOutputStream before invoking the writeObject(Object, String) method.

Modify the writeObject method of Movie.java so that it looks like Listing 3-13.

Listing 3-13  The writeObject method in Movie.java using XML serialization with keys

private void writeObject(ObjectOutputStream stream) throws IOException {
    // Serialize the object's instance members.
    // (This is where you put special encoding logic;
    // this example doesn't perform any special encoding.)
 
    // Cast stream to NSXMLOutputStream to gain access to
    // <code>writeObject(Object, String)</code>.
    NSXMLOutputStream xml_stream = (NSXMLOutputStream)stream;
 
    xml_stream.writeObject(title(), "title");
    xml_stream.writeObject(studio(), "studio");
    xml_stream.writeObject(releaseDate().toString(), "release_date");
}

Now, modify the serializeMovie, deserializeMovie, serializeMovieArray and deserializeMovieArray methods in Application.java so that they use the XMLSerializer class's serializeObject and deserializeObject methods, respectively.

Build and run the application. If you open /tmp/Movies_data.xml in a text editor, you'll see something similar to the contents of Listing 3-14. Notice the key attribute included in the data elements of each object element corresponding to a Movie object. The key attribute is used by the transformation script in Transforming an Array of Movies to generate the data elements of the target document.

Also notice that the third Movie object in Movies_data.xml (starting at the line numbered 2) contains a reference to the studio defined in the first Movie object (the line numbered 1) instead of the name of the studio. This is how multiple references to the same object are represented in XML documents generated by NSXMLOutputStream.

Listing 3-14  The Movies_data.xml file

<?xml version="1.0" encoding="UTF-8"?>
<content xmlns="http://www.apple.com/webobjects/XMLSerialization"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.apple.com/webobjects/XMLSerialization
http://www.apple.com/webobjects/5.2/schemas/woxml.xsd">
    <object id="3">
        <class flag="2" id="0"
            name="com.webobjects.foundation.NSMutableArray" suid="-3909373569895711876">
            <super flag="3" id="1"
                name="com.webobjects.foundation.NSArray" suid="-3789592578296478260">
                <field name="objects" type="java.lang.Object[]"/>
            </super>
        </class>
        <array field="objects" id="5" ignoreEDB="1" length="3" type="java.lang.Object[]">
            <object id="10">
                <class flag="3" id="6" name="Movie" suid="-791832868721905865">
                    <field name="releaseDate" type="com.webobjects.foundation.NSTimestamp"/>
                    <field name="studio" type="java.lang.String"/>
                    <field name="title" type="java.lang.String"/>
                </class>
                <string id="11" key="title" xml:space="preserve">Alien</string>
                <string id="12" key="studio" xml:space="preserve">20th Century Fox</string>
                <string id="13" ignoreEDB="1" key="release_date" xml:space="preserve">1979-10-25 07:00:00 Etc/GMT</string>
            </object>
            <object id="14">
                <class idRef="6" name="Movie"/>
                <string id="15" key="title" xml:space="preserve">Blade Runner</string>
                <string id="16" key="studio" xml:space="preserve">Warner Brothers</string>
                <string id="17" ignoreEDB="1" key="release_date" xml:space="preserve">1982-01-03 08:00:00 Etc/GMT</string>
            </object>
            <object id="18">
                <class idRef="6" name="Movie"/>
                <string id="19" key="title" xml:space="preserve">Star Wars</string>
                <string idRef="12" key="studio"/>
                <string id="20" ignoreEDB="1" key="release_date" xml:space="preserve">1977-12-29 08:00:00 Etc/GMT</string>
            </object>
        </array>
    </object>
</content>

Formatting Serialized Output

The XML documents produced so far are nicely indented to facilitate their comprehension by people. However, most of the time, these documents are intended for applications. Therefore, indentation (and the extra characters it adds to a stream) is not needed. You can determine whether the output produced by an NSXMLOutputStream object is indented using a NSXMLOutputFormat (com.webobjects.foundation.xml) object. NSXMLOutputFormat objects encapsulate formatting information that can be applied to an NSXMLOutputStream object.

Table 3-1 lists the output-format properties you can set with NSXMLOutputFormat.

Table 3-1  Output-format properties accessible through NSXMLOutputFormat

Property

Description

Default value

encoding

Determines the encoding used in the document.

"UTF-8"

indenting

Determines whether the XML document generated is indented.

true

omitXMLDeclaration

Determines whether the XML declaration is omitted from the document.

false

version

Determines the document's XML version.

"1.0"

You can use WebObjects-style accessors to get and set the values of the properties listed in Table 3-1. The value of the indenting property can also be set through one of the constructors of NSXMLOutputFormat, NSXMLOutputFormat(boolean).

Listing 3-15 shows a method that creates an XML stream to a file and sets the indenting and encoding properties for the stream.

Listing 3-15  Setting the indenting and encoding properties of an NSXMLOutputFormat object and applying them to an NSXMLOutputStream object

/**
 * Opens an XML output stream to a file.
 *
 * @param filename   fully qualified filename of the
 *                   target or source file; identifies
 *                   the channel to open
 *
 * @return object stream, <code>null</code> when the stream
 *         could not be created.
 */
public Object xmlOutputStream(String filename) throws IOException {
    BufferedOutputStream file_output_stream;
    NSXMLOutputStream xml_stream;
    NSXMLOutputFormat format;
 
    // Create an output stream to the file.
    file_output_stream = new BufferedOutputStream(new FileOutputStream(filename));
 
    // Create object output stream.
    xml_stream = new NSXMLOutputStream(file_output_stream);
 
    // Set the format of the output document.
    format = new NSXMLOutputFormat(true);  // turn indentation on
    format.setEncoding("UTF-16");          // set encoding to UTF-16
    xml_stream.setOutputFormat(format);    // apply format to the stream
 
    return xml_stream;
}

For more information on NSXMLOutputFormat, see the API documentation.