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
.
This facility is similar to existing products such as JSX or XStream, with the following advantages:
ByteBuffer
),
performance on a par or better than default JavaTM Serialization/Deserialization
(benchmark data).Serializable
) to be implemented. The default XML mapping for a class and its sub-classes is defined using
a static final
{@link javolution.xml.XmlFormat XmlFormat} instance.
For example:
public abstract class Graphic { private boolean _isVisible; private Paint _paint; // null if none. private Stroke _stroke; // null if none. private Transform _transform; // null if none. // XML format with positional associations for child elements // (see {@link javolution.xml.XmlFormat XmlFormat} for examples of type-based associations). protected static final XmlFormat GRAPHIC_XML = new XmlFormat(Graphic.class) { public void format(Object obj, XmlElement xml) { Graphic g = (Graphic) obj; xml.setAttribute("isVisible", g._isVisible); xml.getContent().addLast(g._paint); xml.getContent().addLast(g._stroke); xml.getContent().addLast(g._transform); } public Object parse(XmlElement xml) { Graphic g = (Graphic) xml.object(); g._isVisible = xml.getAttribute("isVisible", true); g._paint = (Paint) xml.getContent().removeFirst(); g._stroke = (Stroke) xml.getContent().removeFirst(); g._transform = (Transform) xml.getContent().removeFirst(); return g; } }; }Sub-classes may override the inherited XML format:
public class Area extends Graphic { private Surface _geometry; // Adds geometry (surface) to format. protected static final XmlFormat AREA_XML = new XmlFormat(Area.class) { public void format(Object obj, XmlElement xml) { Area area = (Area) obj; GRAPHIC_XML.format(area, xml); // Calls parent format. xml.getContent().addLast(area._geometry); } public Object parse(XmlElement xml) { Area area = (Area) GRAPHIC_XML.parse(xml); // Calls parent parse. area._geometry = (Surface) xml.getContent().removeFirst(); return area; } }; }The following writes a graphic area to a file, then reads it:
new ObjectWriter().write(area, new FileOutputStream("C:/area.xml")); Area a = (Area) new ObjectReader().read(new FileInputStream("C:/area.xml"));
For multiple objects transmissions over open I/O streams, {@link javolution.xml.XmlInputStream} and {@link javolution.xml.XmlOutputStream} are recommended.
Here is an example of valid XML representation for an area:<graphics:Area xmlns:graphics="java:org.jscience.graphics" isVisible="true"> <graphics:Color rgb="#F3EBC6"/> <Null/> <Null/> <graphics:geom2d.Polygon id="1"> <graphics:geom2d.Point x="123" y="-34"/> <graphics:geom2d.Point x="-43" y="-34"/> <graphics:geom2d.Point x="-12" y="123"/> </graphics:geom2d.Polygon> </graphics:Area>
XML formats can be dynamically created or modified. The following illustrates
the creation of a xml format for double[]
instances using
java.lang.Double
objects.
// XML format for java.lang.Double XmlFormat doubleXml = new XmlFormat() { public void format(Object obj, XmlElement xml) { xml.setAttribute("value", ((Double)obj).doubleValue()); } public Object parse(XmlElement xml) { return new Double(xml.getAttribute("value", 0.0)); } }; XmlFormat.setInstance(doubleXml, java.lang.Double.class); // {@link javolution.realtime.LocalContext Local} setting. // XML format for double[] XmlFormat doubleArrayXml = new XmlFormat() { public void format(Object obj, XmlElement xml) { double[] values = (double[])obj; xml.setAttribute("length", values.length); for (int i=0; i < values.length;) xml.getContent().add(new Double(values[i++])); } public Object parse(XmlElement xml) { int length = xml.getAttribute("length", 0); double[] values = new double[length]; Iterator i=xml.getContent().fastIterator(); for (int j=0; j < length;) values[j++] = ((Double)i.next()).doubleValue(); return values; } }; XmlFormat.setInstance(doubleArrayXml, new double[0].getClass()); // {@link javolution.realtime.LocalContext Local} setting. // Replaces default "[D" tag with "double[]" (easier to read) XmlFormat.setAlias(doubleArrayXml.getMappedClass(), "double[]");
An alternative implementation could utilize {@link javolution.realtime.Realtime real-time} numbers
instead of java.lang.Double
(faster and no garbage generated when
marshalling/unmarshalling is performed in a {@link javolution.realtime.PoolContext PoolContext}).
The {@link javolution.xml.XmlFormat XmlFormat} does not have to use the class public default constructor ({@link javolution.xml.XmlElement#object xml.object()}), 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).
Cross-references are supported for formats having an {@link javolution.xml.XmlFormat#identifier identifier} attribute. For example:
public class Polygon extends Surface { FastList _vertices = new FastList(); protected static final XmlFormat POLYGON_XML = new XmlFormat(Polygon.class) { public String identifier(boolean isReference) { return isReference ? "ref" : "id"; } public void format(Object obj, XmlElement xml) { Polygon polygon = (Polygon) obj; xml.getContent().addAll(polygon._vertices); } public Object parse(XmlElement xml) { Polygon polygon = (Polygon) xml.object(); polygon._vertices.addAll(xml.getContent()); return polygon; } }; }Here the XML representation of a list of three polygons, the first and the last one being shared:
<root:java.util.ArrayList xmlns:root="java:" xmlns="java:org.jscience.graphics.geom2d> <Polygon id="1"> <Point x="123" y="-34"/> <Point x="-43" y="-34"/> <Point x="-12" y="123"/> </Polygon> <Polygon id="2"> <Point x="-43" y="-34"/> <Point x="123" y="-34"/> <Point x="-12" y="123"/> </Polygon> <Polygon ref="1"/> </root:java.util.ArrayList>The value of the identifier attribute can be set explicitly. For example:
public abstract class Person { private String _name; protected static final XmlFormat PERSON_XML = new XmlFormat(Person.class) { public String identifier(boolean isReference) { return isReference ? "ref" : "name"; } public void format(Object obj, XmlElement xml) { Person person = (Person) obj; xml.setAttribute("name", person._name); // Sets identifier value explicitly. } public Object parse(XmlElement xml) { Persone person = (Person) xml.object(); person._name = xml.getAttribute("name", ""); return person; } }; }
Circular references are supported for formats having the {@link javolution.xml.XmlFormat#preallocate XmlFormat.preallocate(xml)} method implemented.
Finally, here is a code excerpt illustrating how objects can be
efficiently transmitted over the network using the java.nio
facility
instead of classic I/O (slower):
// Client thread. ObjectReader or = new ObjectReader(); ByteBuffer bb = ByteBuffer.allocateDirect(XML_SIZE); SocketChannel sc = SocketChannel.open(new InetSocketAddress(LOCAL_HOST, PORT)); sc.read(bb); // Reads socket into byte buffer. bb.flip(); Object obj = or.read(bb); // Parses byte buffer. bb.clear(); ... // Server thread. ObjectWriter ow = new ObjectWriter(); ByteBuffer bb = ByteBuffer.allocateDirect(XML_SIZE); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(PORT)); SocketChannel sc = ssc.accept(); // Waits for connections. ow.write(obj, bb); // Formats object into byte buffer. bb.flip(); sc.write(bb); // Sends byte buffer. bb.clear();
When using NIO, the ByteBuffer
capacity has to be large enough to hold
the largest XML representation of the objects being transmitted.