Package javolution.xml

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.

See: Description

Package javolution.xml Description

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 XMLFormat instance. For example:

      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> GRAPHIC_XML = new XMLFormat<Graphic>(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");
              }
          };
      }
Sub-classes may override the inherited XML format:
      public class Area extends Graphic {
          private Shape _geometry;  
        
          // Adds geometry to format.
          protected static final XMLFormat<Area> AREA_XML = new XMLFormat<Area>(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");
              }
          };
      }
The following writes a graphic area to a file, then reads it:
    
      // 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();
      
Here is an example of valid XML representation for an area:
      <Area isVisible="true">
          <Paint type="Color" rgb="#F3EBC6" />
          <Geometry type="Polygon">
              <Vertex x="123" y="-34" />
              <Vertex x="-43" y="-34" />
              <Vertex x="-12" y="123" />
          </Geometry>
      </Area>

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

XML FORMAT XML DATA
XMLFormat<Foo> XML = new XMLFormat<Foo>(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", "");
    }
};
 <!-- Member as attribute -->
 <Foo text="This is a text"/>
XMLFormat<Foo> XML = new XMLFormat<Foo>(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();
    }
};
 <!-- Member as anonymous nested element -->
 <Foo>
     <java.lang.String value="This is a text"/>
 </Foo>
XMLFormat<Foo> XML = new XMLFormat<Foo>(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.
    }
};
 <!-- Member as Character Data -->
 <Foo>This is a text</Foo>
XMLFormat<Foo> XML = new XMLFormat<Foo>(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");
    }
};
 <!-- Member as named element of unknown type  -->
 <Foo>
     <Text class="java.lang.String" value="This is a text"/>
 </Foo>
XMLFormat<Foo> XML = new XMLFormat<Foo>(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);
    }
};
 <!-- Member as named element of actual type known -->
 <Foo>
     <Text value="This is a text"/>
 </Foo>

The 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:

        public final class Point implements XMLSerializable { 
            // Default XMLFormat can be private as the class cannot be extended.
            static final XMLFormat<Point> XML = new XMLFormat<Point>(Point.class) {
                public boolean isReferencable() {
                    return false; // Always manipulates by value.
                }
                public Point newInstance(Class<Point> 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) { ... }
        }

Document cross-references are supported, including circular references. Let's take for example:

        public class Polygon implements Shape, XMLSerializable { 
            private Point[] _vertices;
            static final XMLFormat<Polygon> POLYGON_XML = new XMLFormat<Polygon>(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);
        
Prints the following (noticed that the first polygon and last one are being shared).
      <Polygons length="3">
          <Polygon id="0" count="3">
              <Vertex x="123" y="-34" />  
              <Vertex x="-43" y="-34" />
              <Vertex x="-12" y="123" />
          </Polygon>
          <Polygon id="1" count="3">
              <Vertex x="-43" y="-34" />
              <Vertex x="123" y="-34" />
              <Vertex x="-12" y="123" />
          </Polygon>
          <Polygon ref="0"/>
      </Polygons>

ALGORITHMS:

Our XMLObjectReader/XMLObjectWriter are in fact simple wrappers around our Javolution high-performance StAX-like XMLStreamReader and 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

Copyright © 2005-2012 Javolution. All Rights Reserved.