Making objects persistent

bject persistence is a fundamental feature of any object-oriented environment. A persistence mechanism enables objects or graphs of objects to record essential state information that allows the object graph to be reconstructed at a later date. At runtime, the IFC library rebuilds the user interface objects using the persistence information.

Because neither the Java language nor its associated libraries provide a persistence mechanism, the IFC library includes a framework that enables this functionality.


The Archive

Within the IFC library, persistence information is managed by an Archive. An Archive is a pseudo-database mechanism for containing persistence information for one or more object graphs. Most objects, even simple ones, depend upon other objects to accomplish their tasks: a Button contains instance variables that refer to the Images it displays; a TextField contains an instance variable for the String holding its contents. These objects might, in turn, rely upon other objects.

An object graph refers to an object and all other objects that it directly or indirectly relies upon. The original object is called the root object. Archiving is the process of storing state information for a root object and other objects in the graph.


The Archiver and Unarchiver

Archives manage state information, but you never work with them directly. Instead, you use an Archiver to archive objects and an Unarchiver to restore objects from the Archive. The following code segment archives an object to an archive:

theArchive = new Archive();
archiver = new Archiver(theArchive);
          
archiver.archiveRootObject(myObject);
The Archive traces the root object's object graph, instructing each object to write its state information into the Archive. Only objects that implement the Codable interface can be written into an Archive. See "The Codable interface" below.

Retrieving objects from an Archive involves asking for the root objects that it contains:

Codable rootObjects[];
          
rootObjects = Unarchiver.unarchiveRoots(theArchive);

The Codable interface

To be written to or reconstituted from an Archive, an object must conform to the Codable interface. The Codable interface defines methods called by the Archiver and Unarchiver to encode or decode an object, respectively:

public interface Codable {
          public void describeClassInfo(ClassInfo info);
          public void encode(Encoder encoder) throws CodingException;
          public void decode(Decoder decoder) throws CodingException;
          public void finishDecoding() throws CodingException;
}

Methods called by the Archiver

The Archiver calls the first two methods, describeClassInfo() and encode(). Data is placed into and retrieved from the Archive by name-type pairs, where name is a string and type is one of the data types defined in the Codable interface, such as TYPE_OBJECT or TYPE_INTEGER. The encode() method contains the code to request that this data be stored:

public class Button extends View {
          Image buttonImage;
          String title;
          boolean enabled;
          final static int VERSION = 1;
          
          public void describeClassInfo(ClassInfo, info){
                    super.describeClassInfo(info);
          
                    info.addClass("netscape.application.Button",VERSION);
                    info.addField("image",TYPE_OBJECT);
                    info.addField("title",TYPE_STRING);
                    info.addField("enabled",TYPE_BOOLEAN);
          }
          
          public void encode(Encoder encoder) throws CodingException{
                    super.encode(encoder);
          
                    encode.encodeObject("image", buttonImage);
                    encode.encodeString("title", title);
                    encode.encodeBoolean("enabled", enabled);
          }
}
In the example above, the Button class has three instance variables that it needs to store: its image, its title, and whether it is enabled. Its describeClassInfo() method declares the name-type pairs it will archive, as well as the class, in the calls to the ClassInfo's addClass() and addField() methods. This method also declares the class' full name, which includes its package, and a version number. The full name allows the Unarchiver to instantiate the proper type of object when recreating objects from the Archive.

The version number allows a class to record the version that archived the information so that future versions can reconstitute themselves properly from the Archive. For example, suppose Button version two includes a background color that it stores in an Archive. Its decode() method can check the version number of the information stored in the Archive. If it encounters data stored by Button version one, rather than attempting to read the background color from the Archive, decode() can just set the color to some default value.

Methods called by the Unarchiver

The decode() and finishDecoding() methods are called by the Unarchiver. The decode() method contains counterpart code to the encode() method to retrieve the object's state from the Archive. The finishDecoding() method is called when all objects have been read from the Archive, providing a chance to perform any post unarchiving processing:

public void decode(Decoder decoder) throws CodingException{
          super.decode(decoder);
          
          buttonImage = (Image)decoder.decodeObject("image");
          title = decoder.decodeString("title");
          enabled = decoder.decodeBoolean("enabled");
}
          
public void finishDecoding() throws CodingException {
          super.finishDecoding();
}
The Button's decode() method retrieves data using the keys that it used to store the data into the Archive. Like most other Archivable implementations, its finishDecoding() method does nothing. The finishDecoding() method allows decoded objects to perform any post-decoding processing at a point at which all objects have been read from the Archive. For example, the View class' superview instance variable is not archived by its encode() method; therefore, it must be restored at some point after unarchiving. The View's finishDecoding() method sets the superview instance variable for all of its subviews to itself. It waits for the finishDecoding() method rather than the decode() method, so that all of its subviews are guaranteed to be completely unarchived.


Saving Archives

The Archive is a data structure, not a storage mechanism, meaning that it does not dictate how its contents are stored in a file or other storage medium. Because it is not a storage mechanism, an Archive's data can be easily mapped to a relational database, binary data stream, or any other storage device. The Archive provides methods that write and read its data using the AsciiSerializer and AsciiDeserializer. The Archive's database-like structure, combined with the human-readable nature of AsciiSerializer output, makes hand-editing of serialized archives very easy. The Archive also provides a more compact binary storage format.

The Archive as a database

The Archive's database-like access to data (using names) provides several advantages to working with Archives.

Archives are random-access containers, meaning that an object can ask for any archived datum at any time during its unarchiving process, including data which does not exist. By contrast, the typical archiving system treats the archive as a binary stream, where data must be read in the exact order that it was written. The binary stream approach also presents the possibility of an object, early in the unarchiving process, reading too few or too many bytes from the stream and confusing objects at a later point in the unarchiving process.

Because an Archive resembles a database, its contents can be examined and changed by any tool built to manipulate its structure, even without the class files of the objects that originally created the Archive. With a binary stream archive, the only way to read the Archive's contents is by reconstituting the objects it contains, because only the code for those objects understands how its data was written into the Archive.