Object references
How does XStream deals with duplicate and circular references?
It depends on XStream's mode, the default is uses XPath to allow serialized objects to be treated as graphs instead of simple trees (typical XML usage).
Sometimes it's not desirable to work this way, let's take a look in a simple example in order to switch between XStream's modes and see what its capable of.
We start with a simple compact-disc class, with id and a bonus cd fields:
package com.thoughtworks.xstream; public class Cd { private String id; private Cd bonusCd; Cd(String id, Cd bonusCd) { this.id = id; this.bonusCd = bonusCd; } Cd(String id) { this.id = id; } public String getId() { return id; } public Cd getBonusCd() { return bonusCd; } }
And let's create an order with the same cd twice and the order itself, so we can simulate two references to the same object and a back-reference.
Cd bj = new Cd("basement_jaxx_singles"); List order = new ArrayList(); // adds the same cd twice (two references to the same object) order.add(bj); order.add(bj); // adds itself (cycle) order.add(order); XStream xstream = new XStream(); xstream.alias("cd", Cd.class); System.out.println(xstream.toXML(order));
If we execute the above code, XStream's uses its default mode called XPATH_RELATIVE_REFERENCES based on the W3C XPath specification. Cross and back references are treated in a way that it's (almost) human readable:
<list> <cd> <id>maria rita</id> </cd> <cd> <id>basement_jaxx_singles</id> </cd> <cd reference="../cd[2]"/> <list reference=".."/> </list>
The second reference to the Basement Jaxx cd was serialized as "../cd[2]" while the order inside itself used the ".." path. The XPath Relative mode allows any type of graphs to be used as both cross and back-references are supported.
In order to make use of the XPath Relative mode one can implicitly call:
xstream.setMode(XStream.XPATH_RELATIVE_REFERENCES);
Relative x Absolute
There is an absolute mode which is easy to use and understand. It works using the same XPath specification and also supports all types of graphs.
xstream.setMode(XStream.XPATH_ABSOLUTE_REFERENCES);
The changes in the resulting xml is a little more 'clutter':
<list> <cd> <id>maria rita</id> </cd> <cd> <id>basement_jaxx_singles</id> </cd> <cd reference="/list/cd[2]"/> <list reference="/list"/> </list>
Single Node Selectors
In some cases where the XML is used later on or is generated by someone else, the XPath selectors can be forced to select always a single node instead of a node list where the first element is taken. Therefore two more modes exist:
xstream.setMode(XStream.SINGLE_NODE_XPATH_ABSOLUTE_REFERENCES); xstream.setMode(XStream.SINGLE_NODE_XPATH_RELATIVE_REFERENCES);
The changes in the resulting xml is even more 'clutter':
<list> <cd> <id>maria rita</id> </cd> <cd> <id>basement_jaxx_singles</id> </cd> <cd reference="/list[1]/cd[2]"/> <list reference="/list[1]"/> </list>
For XStream is the notation with the single node selectors absolutely equivalent to the one without. These two notations are completely transparent at deserialization time.
Id mode
Both modes displayed until now are not so easy to write by hand. XStream has another mode which makes it is easier to read/write by a human being:
xstream.setMode(XStream.ID_REFERENCES);
The result is a XML which generates an "id" attribute for each new object marshaled, and whenever it finds back or cross-references, it uses a "reference" attribute to so it doesn't copy the entire object.
In our example, the list has id 1, the Maria Rita cd 2, and the Basement Jaxx 3. Therefore the cross-reference to our cd should contain a reference attribute to object number 2 and the back-reference to our order a reference to object 1. The result is:
<list id="1"> <cd id="2"> <id>maria rita</id> </cd> <cd id="3"> <id>basement_jaxx_singles</id> </cd> <cd reference="3"/> <list reference="1"/> </list>
No references
For some uses of XStream we do not desire any kind of back or cross references like a graph, but a simple tree. The most famous example is when using XStream to generate XML for B2B services.
In such scenarios it's impossible to represent a graph cycle (remember: no tree contains such structure), therefore we have to remove the last add call from our example:
Cd bj = new Cd("basement_jaxx_singles"); List order = new ArrayList(); // adds the same cd twice (two references to the same object) order.add(bj); order.add(bj); XStream xstream = new XStream(); xstream.alias("cd", Cd.class); System.out.println(xstream.toXML(order));
Now if we add the NO_REFERENCES option, every reference shall be completed serialized.
xstream.setMode(XStream.NO_REFERENCES);
The result are three references:
<list> <cd> <id>maria rita</id> </cd> <cd> <id>basement_jaxx_singles</id> </cd> <cd> <id>basement_jaxx_singles</id> </cd> </list>
After reading from the above XML, you will get three different Cd instances.
Remember: it's impossible to support back-references (cycles) with NO_REFERENCES mode activated therefore a CircularReferenceException is thrown if you ever try to do so.