Chapter 18. Persistence

Table of Contents

XMLStreamWriter and XMLStreamReader
Colors
Serializer and Builder
Serializing Composite Objects
Building Composites

The Tensegrity API provides basic support for writing and reading XML documents through a list of Attribute objects. AttributeList or AttributeSet objects are written out with a Serializer, an interface defining methods used to serialize the objects. The serialized objects are read back and instantiated using a Builder. Classes implementing the Builder interface construct objects using an AttributeList that describes a serialized object.

Before we discuss the individual characteristics of the Builder and Serializer interfaces (section Serializer and Builder, we will take a closer look at the classes responsible for the described functionality.

Figure 18.1. Persistence Writer and Reader

Persistence Writer and Reader

XMLStreamWriter and XMLStreamReader

Class XMLStreamWriter writes an AttributeList object to an XML file. This list may contain AttributeList, AttributeSet and Attributes instances. All Constraint objects that have been assigned to the objects will be stored in the XML file as well.

XML File Structure:

 
- LIST:
 
  <list name=NAME>
      (LIST | SET | ATTRIBUTE)*
      (constraint)+
  </list>
 
      NAME: Name of list (in a set this has to be unique)
 
 
- SET:
 
  <set name=NAME>
      (SET | ATTRIBUTE)*
  </set>
 
       NAME: String name of this set (in a set this has to be unique)
 
 
- ATTRIBUTE:
 
  <attribute name=NAME type=TYPE value=VALUE>
      (CONSTRAINT)+
  </attribute>
 
       NAME: String name of this attribute (in a set this has to be unique)
       TYPE: Full classname of the value of this attribute
      VALUE: Initial value of the instance of this valueobject 
 
 
- CONSTRAINT:
 
  <constraint>
      <![cdata[(EXPR)]]>
  </constraint>
 
  (Instead of the cdata-Part a text node is allowed, too:
   <constraint>value==0</constraint>
   But concerning the special characters (">", ...) it should not be used.)


- EXPR:
 
  String expression that can be parsed by a ConstraintParser.
 
 

Structure of AttributeList:

 
- LIST:

  AttributeList, containing attributes of following types:
      (LIST-ATTRIBUTE | SET-ATTRIBUTE | ATTRIBUTE)*
      May contain a valid constraint of any type.

  
- LIST-ATTRIBUTE:

  Attribute with name and value. 
  In a set the name has to be unique.
  The name will be stored as name of the list. 
  The value is of type AttributeList.
  Must not contain a Constraint, as this is ignored when writing to XML.
  Instead the Constraint should be assigned to the AttributeList
  directly. The list itself may contain the following attributes:
  (LIST-ATTRIBUTE | SET-ATTRIBUTE | ATTRIBUTE)*
 
 
- SET-ATTRIBUTE:

  Attribute with name and value. 
  In a set the name has to be unique.
  The name will be stored as name of the set. The value is of type AttributeSet.
  Must not contain a Constraint, as this is ignored when writing to XML.
  Instead the Constraint should be assigned to the AttributeSet directly.
  The set itself may contain the following attributes:
  (SET-ATTRIBUTE | ATTRIBUTE)*
 
 
- ATTRIBUTE:

  Any Attribute with a name and value. In a set the name has to be unique.
  The value may be of any type that can be recreated/stored by 
  parseToObject() (or storeToString() of XMLWriter).
 

Example 18.1. Sample AttributeList

AttributeFactory attributeFactory = new DefaultAttributeFactory();
    
AttributeList list = attributeFactory.newAttributeList();

Attribute surAttr = 
  attributeFactory.newAttribute("Surname", "Weckl");
Attribute firstAttr = 
  attributeFactory.newAttribute("Firstname", "Dave");
  
list.add(firstAttr);
list.add(surAttr);
    
OutputStream out = new FileOutputStream(new File("test.xml"));
XMLStreamWriter writer = new XMLStreamWriter(out);
writer.write(list);

like this:

Example 18.2. XML Output of the Sample AttributeList

<list>
 <attribute name="Firstname" value="Dave"/>
 <attribute name="Surname" value="Weckl"/>		
</list>

As you can see, the “type” attribute was not added to the attribute tags. This is because the types are both String. Since XML persists exclusively String objects, attributes whose type is String do not need to specify this default type. If an Attribute is specified like this:

Example 18.3. Non-String value as Attribute

Attribute colorAttr = 
    attributeFactory.newAttribute("FavoriteColor", new Color(0x7B004A));

The output of the XMLStreamWriter will look like this:

Example 18.4. XML Output of the Sample AttributeList (2)

<list>
 <attribute name="Firstname" value="Dave"/>
 <attribute name="Surname" value="Weckl"/>
 <attribute name="FavoriteColor" type="Color" value="123, 0, 74"/>
</list>
        

The XMLStreamReader, on the other hand, is responsible for reading an XML file and creating an AttributeList from its entities.

Class XMLStreamReader loads an XML file, parses its contents and creates a runtime AttributeList instance containing Attribute or AttributeSet objects or both. Constraint objects may be assigned to all these objects in the list.

The following seven basic types are explicitly converted by class XMLStreamReader.

  1. String

  2. Double

  3. Float

  4. Integer

  5. Long

  6. Boolean

  7. Color

If a type is encountered that is not within this list, an Attribute with a value of type String is created.

If you wish to store non-basic (not java primitive) types with the XMLStreamWriter, you have to remember to create a canonical string representation for your objects. Once you have created this string representation, you will have to program a method which creates instances of your class using these strings. The following code snippet illustrates how such a class could be coded:

Example 18.5. Sample Type Definition

public class ScalableFactor
{
  	// stores a factor as float
    float xFactor;     
    // flag that indicates scalability
    boolean scalable;  
    
    public ScalableFactor()
    {
        this(1, true);
    }
    
    public ScalableFactor(float xFactor, boolean scalable)
    {
      this.xFactor = xFactor;
      this.scalable = scalable;
    }
    
    // Returns a canonical String representation of this instance
    String getCanonicalRepresentation()
    {
      return 
      	Float.toString(xFactor) + 
      	"#" + scalable;
    }
      
    public String toString()
    {
      return getCanonicalRepresentation();
    }
      
    // Creates a ScalableFactor instance from a string.
    static ScalableFactor createInstance(String s)
    {
      ScalableFactor sf = new ScalableFactor();
      sf.xFactor = Float.parseFloat(s.substring(0, s.indexOf('#')));
      sf.scalable = 
        Boolean.getBoolean(s.substring(s.indexOf('#') + 1));
      
      return sf;
    }
}

An AttributeList containing instances of ScalableFactor would be created like this:

Example 18.6. Sample AttributeList with ScalableFactor instances

AttributeFactory attributeFactory = 
  new DefaultAttributeFactory();
AttributeList list= attributeFactory.newAttributeList();

Attribute topAttr= 
  attributeFactory.newAttribute(
      "TopLine",
      new ScalableFactor(0.5f, true));
Attribute botAttr= 
  attributeFactory.newAttribute(
      "BottomLine",
      new ScalableFactor(0.9f, false));

list.add(topAttr);
list.add(botAttr);

OutputStream out = 
  new FileOutputStream(new File("test.xml"));
        
XMLStreamWriter writer= new XMLStreamWriter(out);
writer.write(list);

The XMLStreamWriter would output this list like:

Example 18.7. XML Output of the AttributeList with a custom type

<list>
 <attribute name="TopLine" type="ScalableFactor" value="0.5#true"/>
 <attribute name="BottomLine" type="ScalableFactor" value="0.9#false"/>
</list>
        

When the XMLStreamReader reaches the point where it has to convert the XML element to an Attribute, it will default to a String representation because the type ScalableFactor is not known. It will subsequently create an Attribute which looks like this:

Example 18.8. XMLReader Attribute creation

Attribute topAttr = 
  attributeFactory.newAttribute("TopLine", "0.5#true");

A Builder can then restore the instance by using the following code:

Example 18.9. Restoring objects from String

public Object build(AttributeList attributes) 
	throws BuildException
{
  // get the attribute value of attribute "TopLine"
  String topLineString = 
    (String) BuilderUtilities.getAttributeValue(
      attributes, "TopLine", String.class);
      
  // create the instance from the string representation
  ScalableFactor topLineFactor = 
    ScalableFactor.createInstance(topLineString);
  
  return topLineFactor;
}

Colors

To improve the overall readability of an XML file, a custom wrapper for java.awt.Color instances has been provided to be used internally by the XMLStreamReader and XMLStreamWriter classes. Every time a java.awt.Color instance is encountered by a writer, its value is converted into a more readable version by a ColorParser object. When a reader later encounters an Attribute of type java.awt.Color, it uses this parser to convert the stored values back to an instance of type java.awt.Color. The following table illustrates how you can define color values within your XML file:

Table 18.1. Color definition

NameDescriptionSample
RGB The most obvious way to define a color by using RGB values. The values must be comma-separated (“,”) and each value must be in the range of 0 to 255. <attribute name="wallpaper" type="Color" value="12, 0, 255"/>
Hexadecimal The hexadecimal representation is a value that starts with a pound sign (“#”) followed by six hexadecimal digits, whereby 2 subsequent digits represent a single R, G or B value and must be in the range of 00 to FF. <attribute name="wallpaper" type="Color" value="#00FFAA"/>
String To simplify the representation, we also offer the use of “human readable” strings to define a color. You can choose from the predefined names “black”, “white”, “gray”, “red”, “pink”, “orange”, “yellow”, “green”, “magenta”, “cyan” and “blue”. Furthermore, you can add the prefix “light” or “dark” to alter the color (this doesn't apply to “black” and “ white”). <attribute name="wallpaper" type="Color" value="darkorange"/>