Persistence

The package marimba.persist defines a set of simple classes which are used to read and write objects to files and streams. The implementation is entirely in Java and does not use any special language features or tricks.

An object that wants to be saved to a file simply needs to implement the interface PropertyObject containing two methods, so that it can be persistified to a file or to the clipboard. The same mechanism is used to edit object properties.


PropertyObject

In order to save, load, manipulate, and examine objects a simple mechanism for persistifying object is provided. The mechanism is based on typed property lists.

The PropertyObject interface defines two methods for getting and setting the properties of an object:

    package marimba.persist;

    public interface PropertyObject {
        /**
         * Get the properties from the object and store
         * them in the property list.
         */
        void getProperties(PropertyList list);

        /**
         * Set the object's properties from the property list.
         */
        void setProperties(PropertyList list);
    }
The getProperties method is called to get properties from the object and store them in a property list. The resulting property list describes the object in every detail, and can be used to clone the object, or it can be saved to a file.

The setProperties method is called to set the properties of an object given the property list. This method is called when an object is reconstituted. The object should take the relevant properties from the list and store them in instance variables.


PropertyList

The properties that are retrieved from an object are stored in a PropertyList which has the following methods:
    public abstract class PropertyList {
        public abstract void setBoolean(String nm, boolean val, boolean def);
        public abstract void setInteger(String nm, int val, int def);
        public abstract void setLong(String nm, long val, long def);
        public abstract void setString(String nm, String val, String def);
        public abstract void setOption(String nm, Options opts, int val, int def);
        public abstract void setColor(String nm, Color val, Color def);
        public abstract void setFont(String nm, Font val, Font def);
        public abstract void setURL(String nm, String val, String def);
        public abstract void setObject(String nm, Object val, Object def);
        public abstract void setByteArray(String nm, byte data[], byte def[]);
        public abstract void setObjectArray(String nm, int len, Object val[]);

        public abstract boolean getBoolean(String nm, boolean def);
        public abstract int     getInteger(String nm, int def);
        public abstract long    getLong(String nm, long def);
        public abstract String  getString(String nm, String def);
        public abstract int     getOption(String nm, Options opts, int def);
        public abstract Color   getColor(String nm, Color def);
        public abstract Font    getFont(String nm, Font def);
        public abstract String  getURL(String nm, String def);
        public abstract Object  getObject(String nm, Object def);
        public abstract byte[]  getByteArray(String nm, byte def[]);
        public abstract Object[] getObjectArray(String nm, Object[] def);
    }
The set methods in this class take three arguments: the symbolic name of the property, the current value of the property, and the default value of the property. The name of the property and the name of the instance variable it represents do not necessarily have to be the same. If the current value and the default value of the property are the same then the property is not stored in the property list.

The get methods of the PropertyList class take two arguments: the symbolic name of the property, and the default value of the property. If the object requests the value of a property which is not in the property list, then the default value is returned.

Example

Here is an example of a simple widget with two instance variables label, and center which need to be preserved between instantiations of the object. The object must store the values of these variables in a property list when the property list is requested, and it must retrieve them from the property list when the object is reinstantiated.
    import marimba.gui.*;
    import marimba.persist.*;

    public class RoundButtonWidget extends Widget {
        String label;
	boolean fill;
	boolean down;

	public RoundButtonWidget() {
	    ...
	}

        public void getProperties(PropertyList list) {
	    // store super class properties in the list
	    super.getProperties(list);

	    // store additional properties in the list
	    list.setBoolean("fill", fill, false);
	    list.setString("label", label, null);
        }

        public void setProperties(PropertyList list) {
	    // retrieve super class properties from the list
	    super.setProperties(list);

	    // retrieve additional properties from list
	    fill = list.getBoolean("fill", false);
	    label = list.getString("label", null);
        }

	...
    }
Note that the object's class must be declare public, and it must have a public default constructor (one with no arguments). Also note that not all instance variables are saved, only the label and fill instance variables are saved. The down instance variable is not saved because it is only used during user interaction.

The source code for this example is included in the release. It is located in the demo/programming folder.


Cyclic References

The property list mechanism takes care of resolving cyclic references. Any objects which are stored in a property list are in turn automatically converted to a property list if they implement the ProperyObject interface. Objects that occur multiple times in the same property list closure will be converted only once.

Objects in property lists which do not implement the PropertyObject interface are converted to null references.


File Format

To save an object, or a set of objects to a file, the system will first convert the objects to a property list. Once the property list is created it is written to a file.

The file format is not defined by the property objects, instead it is the system which chooses the most efficient format in which to store the property list. The current system uses a very efficient compressed format which can contain many cyclic references without creating any duplicate property values.

To load an object from a file the system will read the object's property list into memory before the object is reinstantiated. The object is reinstantiated by calling the default constructor (with no arguments), followed by a call to setProperties.

The system will automatically reestablish any cyclic references that were part of the original persistified data structure.

Here is how an arbitrary object is saved to a file. This uses the BinaryPersistentState class from the marimba.persist package. Saving and loading objects to and from files is usually done by the system, but it can be done explicitly as shown below:

    void save(FastOutputStream out, PropertyObject object) throws IOException {
        // Create a persistent state
        BinaryPersistentState state = new BinaryPersistentState();

	// Freeze the object
        PersistentObject frozen = state.freeze(object);

	// Save the frozen state to the stream
	state.save(out, frozen);
    }
Here is how an arbitrary object is loaded from a file. This is normally taken care of by the system.
    PropertyObject load(FastInputStream in) throws IOException {
        // Create a persistent state
        BinaryPersistentState state = new BinaryPersistentState();

	// Load the frozen object
        PersistentObject frozen = state.loadPersistentObject(in);

	// Thaw the frozen object
	return state.thaw(frozen);
    }
Actually, property objects can be directly written to a FastOutputStream using the writeObject method. They can be read from a FastInputStream using the readObject method. For example:
    void save(FastOutputStream out, PropertyObject object) {
        out.writeObject(object);
    }

    PropertyObject load(FastInputStream in) {
        return in.readObject();
    }

Versioning

The system does not provide any explicit mechanisms for versioning property lists. However, in practice this is very easily achieved using the standard API. For example, to provide backward compatibility the getProperties method of an object can check if old properties are defined in the property list. If that is the case, the the property list is probably an older version.

In addition objects can define a property called "version" which contains defines the version of the properties. The getProperties method can use the version property to determine how to examine the properties.

In general the property list mechanism provides a lot of flexibility to the programmer to evolve objects between versions. The fact that each get method in a PropertyList requires a default ensures that even undefined properties will always have a reasonable value.


Editing Properties

The property list mechanism is used in Bongo to edit the properties of an object. In that scenario the system creates a property list using a special PropertyList subclass which records the names, types, values, and default values of all the properties. The resulting property list description is used in the property list editor to edit the properties.

When the modified properties are applied the setProperties method of the original object is called and the new property values are applied.


Persistent Data Structure Example

Here is a more complete example of a binary tree data structure which can be persistified:
    public class BinaryTreeNode implements PropertyObject {
	String value;
	BinaryTreeNode left;
	BinaryTreeNode right;

	public BinaryTreeNode() {
	    ...
	}

        public void getProperties(PropertyList list) {
	    // store properties in the list
	    list.setString("value", value, "");
	    list.setObject("left", left, null);
	    list.setObject("right", right, null);
        }

        public void setProperties(PropertyList list) {
	    // retrieve properties from list
	    value = list.getString("value", "");
	    left = (BinaryTreeNode)list.getObject("left", null);
	    right = (BinaryTreeNode)list.getObject("right", null);
        }

	...
    }
The source code for this example is included in the release. It is located in the demo/programming folder.