Provides support for the encoding of objects, and the objects reachable from them, into XML; and the complementary reconstruction of the object graph from XML.

XML marshalling/unmarshalling facility:

XML Data Binding

Key Advantages:

The default XML mapping for a class and its sub-classes is typically defined using a static final {@link javolution.xml.XMLFormat XMLFormat} instance. For example:[code] public abstract class Graphic implements XMLSerializable { private boolean _isVisible; private Paint _paint; // null if none. private Stroke _stroke; // null if none. private Transform _transform; // null if none. // Default XML format with name associations (members identified by an unique name). // See XMLFormat for examples of positional associations. protected static final XMLFormat GRAPHIC_XML = new XMLFormat(Graphic.class) { public void write(Graphic g, OutputElement xml) throws XMLStreamException { xml.setAttribute("isVisible", g._isVisible); xml.add(g._paint, "Paint"); xml.add(g._stroke, "Stroke"); xml.add(g._transform, "Transform"); } public void read(InputElement xml, Graphic g) throws XMLStreamException { g._isVisible = xml.getAttribute("isVisible", true); g._paint = xml.get("Paint"); g._stroke = xml.get("Stroke"); g._transform = xml.get("Transform"); } }; }[/code] Sub-classes may override the inherited XML format:[code] public class Area extends Graphic { private Shape _geometry; // Adds geometry to format. protected static final XMLFormat AREA_XML = new XMLFormat(Area.class) { public void write(Area area, OutputElement xml) throws XMLStreamException { GRAPHIC_XML.write(area, xml); // Calls parent write. xml.add(area._geometry, "Geometry"); } public void read(InputElement xml, Area area) throws XMLStreamException { Graphic.XML.read(xml, area); // Calls parent read. area._geometry = xml.get("Geometry"); } }; }[/code] The following writes a graphic area to a file, then reads it:[code] // Creates some useful aliases for class names. XMLBinding binding = new XMLBinding(); binding.setAlias(Color.class, "Color"); binding.setAlias(Polygon.class, "Polygon"); binding.setClassAttribute("type"); // Use "type" instead of "class" for class attribute. // Writes the area to a file. XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream("C:/area.xml")); writer.setBinding(binding); // Optional. writer.setIndentation("\t"); // Optional (use tabulation for indentation). writer.write(area, "Area", Area.class); writer.close(); // Reads the area back XMLObjectReader reader = XMLObjectReader.newInstance(new FileInputStream("C:/area.xml")); reader.setBinding(binding); Area a = reader.read("Area", Area.class); reader.close(); [/code] Here is an example of valid XML representation for an area:[code] [/code]

The following table illustrates the variety of XML representations supported (Foo class with a single String member named text):

XML FORMAT XML DATA
[code]XMLFormat XML = new XMLFormat(Foo.class) { public void write(Foo foo, OutputElement xml) throws XMLStreamException { xml.setAttribute("text", foo.text); } public void read(InputElement xml, Foo foo) throws XMLStreamException { foo.text = xml.getAttribute("text", ""); } };[/code]
 <!-- Member as attribute -->
 <Foo text="This is a text"/>
[code]XMLFormat XML = new XMLFormat(Foo.class) { public void write(Foo foo, OutputElement xml) throws XMLStreamException { xml.add(foo.text); } public void read(InputElement xml, Foo foo) throws XMLStreamException { foo.text = xml.getNext(); } };[/code]
 <!-- Member as anonymous nested element -->
 <Foo>
     <java.lang.String value="This is a text"/>
 </Foo>
[code]XMLFormat XML = new XMLFormat(Foo.class) { public void write(Foo foo, OutputElement xml) throws XMLStreamException { xml.addText(foo.text); // or xml.getStreamWriter().writeCDATA(foo.text) to use CDATA block. } public void read(InputElement xml, Foo foo) throws XMLStreamException { foo.text = xml.getText().toString(); // Content of a text-only element. } };[/code]
 <!-- Member as Character Data -->
 <Foo>This is a text</Foo>
[code]XMLFormat XML = new XMLFormat(Foo.class) { public void write(Foo foo, OutputElement xml) throws XMLStreamException { xml.add(foo.text, "Text"); } public void read(InputElement xml, Foo foo) throws XMLStreamException { foo.text = xml.get("Text"); } };[/code]
 <!-- Member as named element of unknown type  -->
 <Foo>
     <Text class="java.lang.String" value="This is a text"/>
 </Foo>
[code]XMLFormat XML = new XMLFormat(Foo.class) { public void write(Foo foo, OutputElement xml) throws XMLStreamException { xml.add(foo.text, "Text", String.class); } public void read(InputElement xml, Foo foo) throws XMLStreamException { foo.text = xml.get("Text", String.class); } };[/code]
 <!-- Member as named element of actual type known -->
 <Foo>
     <Text value="This is a text"/>
 </Foo>

The {@link javolution.xml.XMLFormat XMLFormat} does not have to use the class public no-arg constructor, instances can be created using factory methods, private constructors (with constructor parameters set from the XML element) or even retrieved from a collection (if the object is shared or unique). For example:[code] public final class Point implements XMLSerializable { // Default XMLFormat can be private as the class cannot be extended. static final XMLFormat XML = new XMLFormat(Point.class) { public boolean isReferencable() { return false; // Always manipulates by value. } public Point newInstance(Class cls, InputElement xml) throws XMLStreamException { return Point.valueOf(xml.getAttribute("x", 0), xml.getAttribute("y", 0)); } public void write(Point point, OutputElement xml) throws XMLStreamException { xml.setAttribute("x", point._x); xml.setAttribute("y", point._y); } public void read(InputElement xml, Point point) throws XMLStreamException { // Do nothing immutable. } }; private int _x; private int _y; private Point() {}; // No-arg constructor not visible. public static Point valueOf(int x, int y) { ... } }[/code]

Document cross-references are supported, including circular references. Let's take for example:[code] public class Polygon implements Shape, XMLSerializable { private Point[] _vertices; static final XMLFormat POLYGON_XML = new XMLFormat(Polygon.class) { public void write(Polygon polygon, OutputElement xml) throws XMLStreamException { xml.setAttibutes("count", _vertices.length); for (int i=0; i < _vertices.length; i++) { xml.add(_vertices[i], "Vertex", Point.class); } } public void read(InputElement xml, Polygon polygon) throws XMLStreamException { int count = xml.getAttributes("count", 0); polygon._vertices = new Point[count]; for (int i=0; i < count; i++) { _vertices[i] = xml.get("Vertex", Point.class); } } }; } Polygon[] polygons = new Polygon[] {p1, p2, p1}; ... TextBuilder xml = TextBuilder.newInstance(); AppendableWriter out = new AppendableWriter().setOutput(xml) XMLObjectWriter writer = XMLObjectWriter.newInstance(out); writer.setXMLReferenceResolver(new XMLReferenceResolver()); // Enables cross-references. writer.write(polygons, "Polygons", Polygon[].class); writer.close(); System.out.println(xml); [/code] Prints the following (noticed that the first polygon and last one are being shared).[code] [/code]

ALGORITHMS:

Our {@link javolution.xml.XMLObjectReader XMLObjectReader}/{@link javolution.xml.XMLObjectWriter XMLObjectWriter} are in fact simple wrappers around our Javolution high-performance StAX-like {@link javolution.xml.stream.XMLStreamReader XMLStreamReader} and {@link javolution.xml.stream.XMLStreamWriter XMLStreamWriter} classes. The logic of these wrappers is described below:


OutputElement.add(object, name, uri, class):

1. if (object == null) return

2. getStreamWriter().writeStartElement(uri, name)

3. isReference = referenceResolver.writeReference(object, this)
   
4. if (!isReference) binding.getFormat(class).write(object, this)

5. getStreamWriter().writeEndElement()

6. end


InputElement.get(name, uri, class):

1. if (!getStreamReader().getLocalName().equals(name) ||
        !getStreamReader().getNamespaceURI().equals(uri)) return null
        
2. object = referenceResolver.readReference(inputElement)
   
3. if (object != null) Goto 8 // Found reference

4. format = binding.getFormat(class)

5. object = format.newInstance(class, inputElement)

6. referenceResolver.createReference(object, inputElement) // Done before parsing to support circular references.

7. format.read(inputElement, object)

8. getStreamReader().nextTag()

9. end