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.
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.
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.
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.
Objects in property lists which do not implement the PropertyObject interface are converted to null references.
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(); }
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.
When the modified properties are applied the setProperties method of the original object is called and the new property values are applied.
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.