Anda di halaman 1dari 42

Xstream tute Create classes to be serialized Here's a couple of simple classes.

XStream can convert instances of these to XML and back again. public class Person { private String firstname; private String lastname; private PhoneNumber phone; private PhoneNumber fax; // ... constructors and methods } public class PhoneNumber { private int code; private String number; // ... constructors and methods } Note: Notice that the fields are private. XStream doesn't care about the visibility of the fields. No getters or setters are needed. Also, XStream does not limit you to having a default constructor. Initializing XStream To use XStream, simply instantiate the XStream class: XStream xstream = new XStream(); You require xstream-[version].jar and xpp3-[version].jar in the classpath. XPP3 is a very fast XML pull-parser implementation. If you do not want to include this dependency, you can use a standard JAXP DOM parser instead: XStream xstream = new XStream(new DomDriver()); // does not require XPP3 library Note: This class is a simple facade designed for common operations. For more flexibility you may choose to create your own facade that behaves differently. Now, to make the XML outputted by XStream more concise, you can create aliases for your custom class names to XML element names. This is the only type of mapping required to use XStream and even this is optional. xstream.alias("person", Person.class); xstream.alias("phonenumber", PhoneNumber.class); Note: This is an optional step. Without it XStream would work fine, but the XML element names would contain the fully qualified name of each class (including package) which would bulk up the XML a bit. See the Alias Tutorial a more complete introduction.

Serializing an object to XML Let's create an instance of Person and populate its fields: Person joe = new Person("Joe", "Walnes"); joe.setPhone(new PhoneNumber(123, "1234-456")); joe.setFax(new PhoneNumber(123, "9999-999")); Now, to convert it to XML, all you have to do is make a simple call to XStream: String xml = xstream.toXML(joe); The resulting XML looks like this: <person> <firstname>Joe</firstname> <lastname>Walnes</lastname> <phone> <code>123</code> <number>1234-456</number> </phone> <fax> <code>123</code> <number>9999-999</number> </fax> </person> It's that simple. Look at how clean the XML is. Deserializing an object back from XML To reconstruct an object, purely from the XML: Person newJoe = (Person)xstream.fromXML(xml); And that's how simple XStream is! Summary To recap: Create element name to class name aliases for any custom classes using xstream.alias(String elementName, Class cls); Convert an object to XML using xstream.toXML(Object obj); Convert XML back to an object using xstream.fromXML(String xml);

Alias Tutorial The problem Suppose that our client has defined a base XML file that we should make XStream read/write: <blog author="Guilherme Silveira"> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog> Based on the XML file above we shall create some model classes and configure XStream to write/read from this format. The model First things first, the classes which shall represent our xml files are shown next, beginning with a simple Blog: package com.thoughtworks.xstream; public class Blog { private Author author; private List entries = new ArrayList(); public Blog(Author author) { this.author = author; }

public void add(Entry entry) { entries.add(entry); } public List getContent() { return entries; } } A basic author with name: package com.thoughtworks.xstream; public class Author { private String name; public Author(String name) { this.name = name; } public String getName() { return name; } } A blog entry contains a title and description: package com.thoughtworks.xstream; public class Entry { private String title, description; public Entry(String title, String description) { this.title = title; this.description = description; } } Although we did not create many getters/setters its up to you to create those you wish or those which make sense. A simple test We can easily instantiate a new blog and use it with xstream: public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); System.out.println(xstream.toXML(teamBlog));

} And the resulting XML is not so nice as we would want it to be: <com.thoughtworks.xstream.Blog> <author> <name>Guilherme Silveira</name> </author> <entries> <com.thoughtworks.xstream.Entry> <title>first</title> <description>My first blog entry.</description> </com.thoughtworks.xstream.Entry> <com.thoughtworks.xstream.Entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </com.thoughtworks.xstream.Entry> </entries> </com.thoughtworks.xstream.Blog> Class aliasing The first thing we shall change is how XStream refers to the com.thoughtworks.xstream.Blog class. We shall name it simply blog: let's create an alias called blog to the desired class: xstream.alias("blog", Blog.class); Using the same idea, we can alias the 'Entry' class to 'entry': xstream.alias("entry", Entry.class); The result now becomes: <blog> <author> <name>Guilherme Silveira</name> </author> <entries> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description>

</entry> </entries> </blog> Omit the 'entries' tag Now let's implement what was called an implicit collection: whenever you have a collection which doesn't need to display it's root tag, you can map it as an implicit collection. In our example, we do not want to display the entries tag, but simply show the entry tags one after another. A simple call to the addImplicitCollection method shall configure XStream and let it know that we do not want to write the entries tag as described above: package com.thoughtworks.xstream; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); xstream.alias("blog", Blog.class); xstream.alias("entry", Entry.class); xstream.addImplicitCollection(Blog.class, "entries"); System.out.println(xstream.toXML(teamBlog)); } } Pay attention to the addImplicitCollection method call: it describes which class and which member variable shall assume the behaviour we described. The result is almost what we wanted: <blog> <author>

<name>Guilherme Silveira</name> </author> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description> Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog> Attribute aliasing The next step is to set the author member variable as an XML attribute. In order to do this, we shall tell XStream to alias the author field of the Blog class as an "author" attribute. xstream.useAttributeFor(Blog.class, "author"); And now it leaves us with one problem: how does XStream converts an Author in a String so it can be written as a XML tag attribute? Let's use the SimpleValueConverter and implement our own Author converter: class AuthorConverter implements SingleValueConverter { } The first method to implement tells XStream which types it can deal with: public boolean canConvert(Class type) { return type.equals(Author.class); } The second one is used to extract a String from an Author: public String toString(Object obj) { return ((Author) obj).getName(); } And the third one does the opposite job: takes a String and returns an Author: public Object fromString(String name) { return new Author(name); } Finally, the entire single value converter, responsible for converting Strings to Objects (Authors in this case) is: class AuthorConverter implements SingleValueConverter { public String toString(Object obj) {

return ((Author) obj).getName(); } public Object fromString(String name) { return new Author(name); } public boolean canConvert(Class type) { return type.equals(Author.class); } } And let's register this converter: public class Test { public static void main(String[] args) { Blog teamBlog = new Blog(new Author("Guilherme Silveira")); teamBlog.add(new Entry("first","My first blog entry.")); teamBlog.add(new Entry("tutorial", "Today we have developed a nice alias tutorial. Tell your friends! NOW!")); XStream xstream = new XStream(); xstream.alias("blog", Blog.class); xstream.alias("entry", Entry.class); xstream.addImplicitCollection(Blog.class, "entries"); xstream.useAttributeFor(Blog.class, "author"); xstream.registerConverter(new AuthorConverter()); System.out.println(xstream.toXML(teamBlog)); } } The result? <blog author="Guilherme Silveira"> <entry> <title>first</title> <description>My first blog entry.</description> </entry> <entry> <title>tutorial</title> <description>

Today we have developed a nice alias tutorial. Tell your friends! NOW! </description> </entry> </blog> You have to be aware, that attribute values normally have to be normalized by the XMP parser as required by the W3C spec. Leading and trailing whitespaces are normally removed as well as sequential ones! Therefore a deserialized string might differ from the value visible in the XML representation. Summing up Using aliases and converters gives you enough power to configure your XML in almost any way you desire. Don't forget to read the converter tutorial to see other type of converters that you can create using XStream.

Annotations Tutorial Motivation Sometimes it can get tedious to call all those XStream aliases/register converter methods or you might simply like the new trend on configuring POJOs: Java annotations. This tutorial will show you how to use some of the annotations provided by XStream in order to make configuration easier. Let's start with a custom Message class: package com.thoughtworks.xstream; package com.thoughtworks.xstream; public class RendezvousMessage { private int messageType;

public RendezvousMessage(int messageType) { this.messageType = messageType; } } Let's code the XStream calls which generate the XML file: package com.thoughtworks.xstream; public class Tutorial { public static void main(String[] args) { XStream stream = new XStream(); RendezvousMessage msg = new RendezvousMessage(15); System.out.println(stream.toXML(msg)); } } Results in the following XML: <com.thoughtworks.xstream.RendezvousMessage> <messageType>15</messageType> </com.thoughtworks.xstream.RendezvousMessage> Aliasing Annotation The most basic annotation is the one responsible for type and field aliasing: @XStreamAlias. Let's annotate both our type and field and run the tutorial method again: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType; public RendezvousMessage(int messageType) { this.messageType = messageType; } } In some strange way, the result is the same. What happened here? XStream does not read this annotation by default as it would be impossible to deserialize the XML code. Therefore we need to tell XStream to read the annotations from this type: public static void main(String[] args) { XStream stream = new XStream();

xstream.processAnnotations(RendezvousMessage.class); RendezvousMessage msg = new RendezvousMessage(15); System.out.println(stream.toXML(msg)); } Note that we have called the processAnnotations method of XStream. This method registers all aliases annotations in the XStream instance passed as first argument. You may also use the overloaded version of this method taking an array of types. The resulting XML is now what we have expected: <message> <type>15</type> </message> If you let XStream process the annotations of a type, it will also process all annotations of the related types i.e. all super types, implemented interfaces, the class types of the members and all their generic types. Implicit Collections Let's add a List of content to our RendezvousMessage. We desire the same functionality obtained with implicit collections: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType; private List<String> content; public RendezvousMessage(int messageType, String ... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } public static void main(String[] args) { XStream stream = new XStream(); xstream.processAnnotations(RendezvousMessage.class); RendezvousMessage msg = new RendezvousMessage(15, "firstPart","secondPart"); System.out.println(stream.toXML(msg)); }

The resulting XML shows the collection name before its elements: <message> <type>15</type> <content class="java.util.Arrays$ArrayList"> <a class="string-array"> <string>firstPart</string> <string>secondPart</string> </a> </content> </message> This is not what we desire therefore we will annotate the content list to be recognized as an implicit collection: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType; @XStreamImplicit private List<String> content; public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } Resulting in an XML which ignores the field name (content) of the list: <message> <type>15</type> <a class="string-array"> <string>firstPart</string> <string>secondPart</string> </a> </message> We are almost there... we still want to remove the 'a' tag, and define each content part with the tag 'part'. In order to do so, let's add another attribute to our implicit collection annotation. The attribute field defines the name of the tag used for data contained inside this collection: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType;

@XStreamImplicit(itemFieldName="part") private List<String> content; public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } Resulting in a cleaner XML: <message> <type>15</type> <part>firstPart</part> <part>secondPart</part> </message> Local Converters Let's create another attribute which defines the timestamp when the message was created: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType; @XStreamImplicit(itemFieldName="part") private List<String> content; private Calendar created = new GregorianCalendar(); public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } Resulting in the following xml: <message> <type>15</type> <part>firstPart</part> <part>secondPart</part> <created>

<time>1154097812245</time> <timezone>America/Sao_Paulo</timezone> </created> </message> Now we face the following problem: We want to use a custom converter locally for this Calendar, but only for this Calendar, this exact field in this exact type. Easy... let's annotate it with the custom converter annotation: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") private int messageType; @XStreamImplicit(itemFieldName="part") private List<String> content; @XStreamConverter(SingleValueCalendarConverter.class) private Calendar created = new GregorianCalendar(); public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } Let's create the custom converter: public class SingleValueCalendarConverter implements Converter { public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { Calendar calendar = (Calendar) source; writer.setValue(String.valueOf(calendar.getTime().getTime())); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(new Date(Long.parseLong(reader.getValue()))); return calendar; } public boolean canConvert(Class type) { return type.equals(GregorianCalendar.class); }

} And we end up with the converter being used and generating the following XML: <message> <type>15</type> <part>firstPart</part> <part>secondPart</part> <created>1154097812245</created> </message> Attributes The client may asks for the type tag to be an attribute inside the message tag, as follows: <message type="15"> <part>firstPart</part> <part>secondPart</part> <created>1154097812245</created> </message> All you need to do is add the @XStreamAsAttribute annotation: @XStreamAlias("message") class RendezvousMessage { @XStreamAlias("type") @XStreamAsAttribute private int messageType; @XStreamImplicit(itemFieldName="part") private List<String> content; @XStreamConverter(SingleValueCalendarConverter.class) private Calendar created = new GregorianCalendar(); public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } Omitting Fields Sometimes a class may contain elements that should not be part of the resulting XML. In our case we may now drop the 'messageType', since we are only interested at the content. This is easy using the @XStreamOmitField annotation:

@XStreamAlias("message") class RendezvousMessage { @XStreamOmitField private int messageType; @XStreamImplicit(itemFieldName="part") private List<String> content; @XStreamConverter(SingleValueCalendarConverter.class) private Calendar created = new GregorianCalendar(); public RendezvousMessage(int messageType, String... content) { this.messageType = messageType; this.content = Arrays.asList(content); } } The resulting XML does not contain the type of the message anymore: <message> <part>firstPart</part> <part>secondPart</part> <created>1154097812245</created> </message> Auto-detect Annotations Until now we have always told you, that you have to call processAnnotation to configure the XStream instance with the present annotations in the different classes. However, this is only half the truth. You can run XStream also in a lazy mode, where it auto-detects the annotations while processing the object graph and configure the XStream instance on-the-fly: package com.thoughtworks.xstream; public class Tutorial { public static void main(String[] args) { XStream stream = new XStream(); xstream.autodetectAnnotations(true); RendezvousMessage msg = new RendezvousMessage(15); System.out.println(stream.toXML(msg)); } }

The resulting XML will look as expected! Nevertheless you have to understand the implications, therefore some words of warning: 1. Chicken-and-egg problem An XStream instance caches all class types processed for annotations. Every time XStream converts an object it will in auto-detection mode first process the object's type and all the types related (as explained above). Therefore it is no problem to serialize an object graph into XML, since XStream will know of all types in advance. This is no longer true at deserialization time. XStream has to know the alias to turn it into the proper type, but it can find the annotation for the alias only if it has processed the type in advance. Therefore deserialization will fail if the type has not already been processed either by having called XStream's processAnnotations method or by already having serialized this type. However, @XStreamAlias is the only annotation that may fail in this case. 2. Concurrency XStream is not thread-safe while it is configured, thread-safety is only guaranteed during marshalling and unmarshalling. However an annotation is defining a change in configuration that is now applied while object marshalling is processed. Therefore will the auto-detection mode turn XStream's marshalling process in a thread-unsafe operation any you may run under certain circumstances into concurrency problems. 3. Exceptions XStream uses a well-defined exception hierarchy. Normally an InitializationException is only thrown while XStream is configured. If annotations are processed on the fly they can be thrown obviously also in a marshalling process. 4. Performance In auto-detection mode XStream will have to examine any unknown class type for annotations. This will slow down the marshalling process until all processed types have been examined once. Please note, that any call to XStream.processAnnotations will turn off the auto-detection mode. Summing up

The XStream annotations support might help you configuring your class mappings in some ways, as the custom configuration will appear in your types, but might not be the solution for other problems, i.e. when you need to map the same type to two different XML 'standards'. Others might claim that the configuration should be clearly stated in a Java class and not mixed with your model, its up to you to pick the best approach in your case: Annotations or direct method calls to the XStream instance. Annotations do not provide more functionality, but may improve convenience.

Converter Tutorial Simple Converter Setting up a simple example This is the most basic converter... let's start with a simple Person: package com.thoughtworks.xstream.examples; public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } So let's create a person and convert it to XML... package com.thoughtworks.xstream.examples; import com.thoughtworks.xstream.XStream;

import com.thoughtworks.xstream.io.xml.DomDriver; public class PersonTest { public static void main(String[] args) { Person person = new Person(); person.setName("Guilherme"); XStream xStream = new XStream(new DomDriver()); System.out.println(xStream.toXML(person)); } } This results in a really ugly XML code which contains the full class name (including package)... <com.thoughtworks.xstream.examples.Person> <name>Guilherme</name> </com.thoughtworks.xstream.examples.Person> So we make use of an 'alias' to change this full class name to something more 'human', for example 'person'. XStream xStream = new XStream(new DomDriver()); xStream.alias("person", Person.class); System.out.println(xStream.toXML(person)); And the outcome is much easier to read (and smaller): <person> <name>Guilherme</name> </person> Now that we have configured a simple class to play with, let's see what XStream converters can do for us... Creating a PersonConverter Let's 1. 2. 3. create a simple converter capable of: telling its capable of converting Person's translating a Person instance in XML translate XML into a new Person

We begin creating the PersonConverter class and implementing the Converter interface: package com.thoughtworks.xstream.examples; import import import import import com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter { public boolean canConvert(Class clazz) { return false; } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { return null; } } Now we tell whoever calls us that we can handle only Person's (and nothing else, including those classes which extends Person). public boolean canConvert(Class clazz) { return clazz.equals(Person.class); } The second step is usually quite clean, unless you are dealing with generic converters. The marshal method is responsible for translating an object to XML. It receives three arguments: 1. the object we are trying to convert 2. the writer were we should output the data 3. the current marshalling context We start casting the object to person: Person person = (Person) value; Now we can output the data... let's start creating a node called fullname and adding the person's name to it: writer.startNode("fullname"); writer.setValue(person.getName()); writer.endNode(); Quite simple huh? public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Person person = (Person) value; writer.startNode("fullname"); writer.setValue(person.getName()); writer.endNode(); }

We could have called start/end node as many times as we would like (but remember to close everything you open)... and conversion usually takes place when calling the setValue method. And now let's go to the unmarshal. We use the moveDown and moveUp methods to move in the tree hierarchy, so we can simply moveDown, read the value and moveUp. Person person = new Person(); reader.moveDown(); person.setName(reader.getValue()); reader.moveUp(); Which gives us the following converter: package com.thoughtworks.xstream.examples; import import import import import com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class PersonConverter implements Converter { public boolean canConvert(Class clazz) { return clazz.equals(Person.class); } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Person person = (Person) value; writer.startNode("fullname"); writer.setValue(person.getName()); writer.endNode(); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Person person = new Person(); reader.moveDown(); person.setName(reader.getValue()); reader.moveUp(); return person; } } Now let's register our converter and see how our application main method looks like: package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class PersonTest { public static void main(String[] args) { Person person = new Person(); person.setName("Guilherme"); XStream xStream = new XStream(new DomDriver()); xStream.registerConverter(new PersonConverter()); xStream.alias("person", Person.class); System.out.println(xStream.toXML(person)); } } Did you notice how we registered our converter? It's a simple call to registerConverter: xStream.registerConverter(new PersonConverter()); The final result is: <person> <fullname>Guilherme</fullname> </person> So you might say... that only changed my tree, I want to convert data! Try using an attribute called fullname in the person tag instead of creating a new child node. An alternative for types with String representation Let's enhance the Person with a String representation, that contains all necessary text to recreate the instance: package com.thoughtworks.xstream.examples; public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; }

public String toString() { return getName(); } } In this case we can simplify our Converter to package com.thoughtworks.xstream.examples; import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConve rter; public class PersonConverter extends AbstractSingleValueConverter { public boolean canConvert(Class clazz) { return clazz.equals(Person.class); } public Object fromString(String str) { Person person = new Person(); person.setName(string); return person; } } But even nicer, our XML is also simplified (using the alias for the Person class). Since the String representation is complete, a nested element is not necessary anymore: <person>Guilherme</person> Date Converter Now that we know how the Converter interface works, let's create a simple calendar converter which uses the locale to convert the information. Our converter will receive the Locale in its constructor and we will keep a reference to it in a member variable: package com.thoughtworks.xstream.examples; import java.util.Locale; import import import import import com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter { private Locale locale; public DateConverter(Locale locale) { super(); this.locale = locale; } public boolean canConvert(Class clazz) { return false; } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { return null; } } Now let's convert anything which extends Calendar: means if instances of class clazz can be assigned to the Calendar class, they extends the abstract class Calendar: public boolean canConvert(Class clazz) { return Calendar.class.isAssignableFrom(clazz); } Let's go for converting a Calendar in a localized string... we first cast the object to Calendar, extract its Date and then use a DateFormat factory method to get a date converter to our localized string. public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Calendar calendar = (Calendar) value; // grabs the date Date date = calendar.getTime(); // grabs the formatter DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); // formats and sets the value

writer.setValue(formatter.format(date)); } And the other way around... in order to unmarshall, we create a GregorianCalendar, retrieves the localized DateFormat instance, parses the string into a Date and puts this date in the original GregorianCalendar: public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { // creates the calendar GregorianCalendar calendar = new GregorianCalendar(); // grabs the converter DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); // parses the string and sets the time try { calendar.setTime(formatter.parse(reader.getValue())); } catch (ParseException e) { throw new ConversionException(e.getMessage(), e); } // returns the new object return calendar; } Note 1: remember that some DateFormat implementations are not thread-safe, therefore don't put your formatter as a member of your converter. Note 2: this implementation will convert other types of Calendar's to GregorianCalendar after save/load. If this is not what you want, change your canConvert method to return true only if class equals GregorianCalendar. So we get the following converter: package com.thoughtworks.xstream.examples; import import import import import import java.text.DateFormat; java.text.ParseException; java.util.Calendar; java.util.Date; java.util.GregorianCalendar; java.util.Locale;

import import import import import import

com.thoughtworks.xstream.converters.ConversionException; com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class DateConverter implements Converter { private Locale locale; public DateConverter(Locale locale) { super(); this.locale = locale; } public boolean canConvert(Class clazz) { return Calendar.class.isAssignableFrom(clazz); } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Calendar calendar = (Calendar) value; Date date = calendar.getTime(); DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); writer.setValue(formatter.format(date)); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { GregorianCalendar calendar = new GregorianCalendar(); DateFormat formatter = DateFormat.getDateInstance(DateFormat.FULL, this.locale); try { calendar.setTime(formatter.parse(reader.getValue())); } catch (ParseException e) { throw new ConversionException(e.getMessage(), e); } return calendar; } } And let's try it out. We create a DateTest class with a main method:

1. 2. 3. 4.

creates a calendar (current date) creates the XStream object registers the converter with a Brazilian Portuguese locale translates the object in XML

Well, we already know how to do all those steps... so let's go: package com.thoughtworks.xstream.examples; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Locale; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.xml.DomDriver; public class DateTest { public static void main(String[] args) { // grabs the current date from the virtual machine Calendar calendar = new GregorianCalendar(); // creates the xstream XStream xStream = new XStream(new DomDriver()); // brazilian portuguese locale xStream.registerConverter(new DateConverter(new Locale("pt", "br"))); // prints the result System.out.println(xStream.toXML(calendar)); } } The result? Well... it depends, but it will be something like: <gregorian-calendar>Sexta-feira, 10 de Fevereiro de 2006</gregorian-calendar> Note: we did not put any alias as gregorian-calendar is the default alias for GregorianCalendar. And now let's try to unmarshal the result shown above: // loads the calendar from the string Calendar loaded = (Calendar) xStream .fromXML("<gregorian-calendar>Sexta-feira, 10 de Fevereiro de 2006</gregorian-calendar>"); And print it using the system locale, short date format:

// prints using the system defined locale System.out.println(DateFormat.getDateInstance(DateFormat.SHORT).f ormat( loaded.getTime())); The result might be something like (if your system locale is American English): 2/10/06 Complex Converter Setting up another example We already defined some classes, so let them glue together: package com.thoughtworks.xstream.examples; public class Birthday { private Person person; private Calendar date; public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } public Calendar getDate() { return date; } public void setDate(Calendar date) { this.date = date; } } While XStream is capable of converting this class without any problem, we write our own custom converter just for demonstration. This time we want to reuse our already written converters for the Person and the Calendar. The canConvert method is plain simple. We convert no derived classes this time, since they might have additional fields. But we reuse the converters registered in XStream for our member fields and handle null values: package com.thoughtworks.xstream.examples;

import java.util.Calendar; import import import import import com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter { public boolean canConvert(Class clazz) { return Birthday.class == clazz; } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Birthday birthday = (Birthday)value; if (birthday.getPerson() != null) { writer.startNode("person"); context.convertAnother(birthday.getPerson()); writer.endNode(); } if (birthday.getDate() != null) { writer.startNode("birth"); context.convertAnother(birthday.getDate()); writer.endNode(); } } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Birthday birthday = new Birthday(); while (reader.hasMoreChildren()) { reader.moveDown(); if ("person".equals(reader.getNodeName())) { Person person = (Person)context.convertAnother(birthday, Person.class); birthday.setPerson(person); } else if ("birth".equals(reader.getNodeName())) { Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class); birthday.setDate(date); } reader.moveUp(); } return birthday;

} } If the implementation of Birthday ensures, that none of its fields could hold a null value, then we could drop the null condition in the marshal method and in unmarshal we could omit the loop as well as the comparison of the tag names: package com.thoughtworks.xstream.examples; import java.util.Calendar; import import import import import com.thoughtworks.xstream.converters.Converter; com.thoughtworks.xstream.converters.MarshallingContext; com.thoughtworks.xstream.converters.UnmarshallingContext; com.thoughtworks.xstream.io.HierarchicalStreamReader; com.thoughtworks.xstream.io.HierarchicalStreamWriter;

public class BirthdayConverter implements Converter { public boolean canConvert(Class clazz) { return Birthday.class == clazz; } public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Birthday birthday = (Birthday)value; writer.startNode("person"); context.convertAnother(birthday.getPerson()); writer.endNode(); writer.startNode("birth"); context.convertAnother(birthday.getDate()); writer.endNode(); } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Birthday birthday = new Birthday(); reader.moveDown(); Person person = (Person)context.convertAnother(birthday, Person.class); birthday.setPerson(person); reader.moveUp(); reader.moveDown(); Calendar date = (Calendar)context.convertAnother(birthday, Calendar.class); birthday.setDate(date);

reader.moveUp(); return birthday; } } Object Streams Tutorial XStream provides alternative implementations of java.io.ObjectInputStream and java.io.ObjectOutputStream, allowing streams of objects to be serialized or deserialized from XML. This is useful when processing large sets of objects, as only one needs to be in memory at a time. Using the Object Streams The interface to the object streaming capabilities of XStream is through the standard java.io.ObjectOutputStream and java.io.ObjectInputStream objects. Example To serialize a stream of objects to XML: ObjectOutputStream out = xstream.createObjectOutputStream(someWriter); out.writeObject(new Person("Joe", "Walnes")); out.writeObject(new Person("Someone", "Else")); out.writeObject("hello"); out.writeInt(12345); out.close(); The resulting XML: <object-stream> <com.blah.Person> <firstname>Joe</firstname> <lastname>Walnes</lastname> </com.blah.Person> <com.blah.Person> <firstname>Someone</firstname> <lastname>Else</lastname> </com.blah.Person> <string>hello</string> <int>123</int> </object-stream> To deserialze the stream of objects from the XML: ObjectInputStream in = xstream.createObjectInputStream(someReader);

Person a = (Person)in.readObject(); Person b = (Person)in.readObject(); String c = (String)in.readObject(); int d = in.readInt(); Considerations Root node Because an XML document can only have a single root node, all the serialized elements must be wrapped in an additional root node. This root node defaults to <object-stream>, as shown in the example above. This can be changed by using the overloaded method: xstream.createObjectOutputStream(Writer writer, String rootNodeName); Close the ObjectOutputStream Remember to call ObjectOutputStream.close(), otherwise the stream will contain incomplete XML. Detecting the end of the ObjectInputStream When there are no more objects left to read in the stream, ObjectInputStream.readObject() (or primitive equivalent) will throw java.io.EOFException. References Normally XStream will not know about references between the different objects that are written individually in the ObjectStream. Nevertheless there is an example in the acceptance tests (MultipleObjectsInOneStreamTest) where such a functionality is realized with the help of a custom MarshallingStrategy. Note, that this implementation is not for general use, since it will ignore some parameters at second invocation, but they do not matter in the demonstrated use case. Additionally those references prevent the objects from being garbage collected, which is a bit counter-productive for the use case of ObjectStreams as described above. So you have to know what you do!

Persistence API Tutorial The problem Suppose that you need a easy way to persist some objects in the file system. Not just one, but a whole collection. The real problem arrives when you start using java.io api in order to create one output stream for each object, showing itself to be really painful - although simple. Imagine that you have the following Java class, a basic Author class (stolen from some other tutorial): package com.thoughtworks.xstream; public class Author { private String name; public Author(String name) { this.name = name; } public String getName() { return name; } } By using the XmlArrayList implementation of java.util.List you get an easy way to write all authors to disk The XmlArrayList (and related collections) receives a StreamStrategy during its construction. This Strategy decides what to do with each of it's elements. The basic implementation - our need - is the FileStreamStrategy, capable of writing different files to a base directory. // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp"));

We can easily create an XmlArrayList from that strategy: // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp")); // creates the list: List list = new XmlArrayList(strategy); Adding elements Now that we have an XmlArrayList object in our hands, we are able to add, remove and search for objects as usual. Let's add five authors and play around with our list: package org.codehaus.xstream.examples; public class AddAuthors { public static void main(String[] args) { // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp")); // creates the list: List list = new XmlArrayList(strategy); // adds four authors list.add(new Author("joe walnes")); list.add(new Author("joerg schaible")); list.add(new Author("mauro talevi")); list.add(new Author("guilherme silveira")); // adding an extra author Author mistake = new Author("mama"); list.add(mistake); } } If we check the /tmp directory, there are five files: 1.xml, 2.xml, 3.xml, 4.xml, 5.xml, each one containing the XML serialized form of our authors. Playing around Let's remove mama from the list and iterate over all authors: package org.codehaus.xstream.examples; public class RemoveMama {

public static void main(String[] args) { // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp")); // looks up the list: List list = new XmlArrayList(strategy); // remember the list is still there! the files 1-5.xml are still in /tmp! // the list was persisted! for(Iterator it = list.iterator(); it.hasNext(); ) { Author author = (Author) it.next(); if(author.getName().equals("mama")) { System.out.println("Removing mama..."); it.remove(); } else { System.out.println("Keeping " + author.getName()); } } } } The result? Keeping joe walnes Keeping joerg schaible Keeping mauro talevi Keeping guilherme silveira Removing mama... Going further From this point on, you can implement different StreamStrategies in order to generate other behaviour using your XmlArrayList collection, or try the other implementations: XmlSet and XmlMap.

Object Streams Tutorial


XStream provides alternative implementations of java.io.ObjectInputStream and java.io.ObjectOutputStream, allowing streams of objects to be serialized or deserialized from XML.

This is useful when processing large sets of objects, as only one needs to be in memory at a time.

Using the Object Streams


The interface to the object streaming capabilities of XStream is through the standard java.io.ObjectOutputStream and java.io.ObjectInputStream objects.

Example
To serialize a stream of objects to XML:
ObjectOutputStream out = xstream.createObjectOutputStream(someWriter); out.writeObject(new Person("Joe", "Walnes")); out.writeObject(new Person("Someone", "Else")); out.writeObject("hello"); out.writeInt(12345); out.close();

The resulting XML:


<object-stream> <com.blah.Person> <firstname>Joe</firstname> <lastname>Walnes</lastname> </com.blah.Person> <com.blah.Person> <firstname>Someone</firstname> <lastname>Else</lastname> </com.blah.Person> <string>hello</string> <int>123</int> </object-stream>

To deserialze the stream of objects from the XML:


ObjectInputStream in = xstream.createObjectInputStream(someReader); Person a = (Person)in.readObject(); Person b = (Person)in.readObject(); String c = (String)in.readObject(); int d = in.readInt();

Considerations
Root node

Because an XML document can only have a single root node, all the serialized elements must be wrapped in an additional root node. This root node defaults to <object-stream>, as shown in the example above. This can be changed by using the overloaded method:
xstream.createObjectOutputStream(Writer writer, String rootNodeName);

Close the ObjectOutputStream


Remember to call ObjectOutputStream.close(), otherwise the stream will contain incomplete XML.

Detecting the end of the ObjectInputStream


When there are no more objects left to read in the stream, ObjectInputStream.readObject() (or primitive equivalent) will throw java.io.EOFException.

References
Normally XStream will not know about references between the different objects that are written individually in the ObjectStream. Nevertheless there is an example in the acceptance tests (MultipleObjectsInOneStreamTest) where such a functionality is realized with the help of a custom MarshallingStrategy. Note, that this implementation is not for general use, since it will ignore some parameters at second invocation, but they do not matter in the demonstrated use case. Additionally those references prevent the objects from being garbage collected, which is a bit counter-productive for the use case of ObjectStreams as described above. So you have to know what you do!

Persistence API Tutorial


The problem
Suppose that you need a easy way to persist some objects in the file system. Not just one, but a whole collection. The real problem arrives when you start using java.io api in order to create one output stream for each object, showing itself to be really painful - although simple.

Imagine that you have the following Java class, a basic Author class (stolen from some other tutorial):
package com.thoughtworks.xstream; public class Author { private String name; public Author(String name) { this.name = name; } public String getName() { return name; } }

By using the XmlArrayList implementation of java.util.List you get an easy way to write all authors to disk The XmlArrayList (and related collections) receives a StreamStrategy during its construction. This Strategy decides what to do with each of it's elements. The basic implementation - our need - is the FileStreamStrategy, capable of writing different files to a base directory.
// prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp"));

We can easily create an XmlArrayList from that strategy:


// prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp")); // creates the list: List list = new XmlArrayList(strategy);

Adding elements
Now that we have an XmlArrayList object in our hands, we are able to add, remove and search for objects as usual. Let's add five authors and play around with our list:
package org.codehaus.xstream.examples; public class AddAuthors { public static void main(String[] args) { // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new // creates the list: List list = new XmlArrayList(strategy); // adds four authors list.add(new Author("joe walnes")); list.add(new Author("joerg schaible")); list.add(new Author("mauro talevi")); list.add(new Author("guilherme silveira"));

File("/tmp"));

// adding an extra author Author mistake = new Author("mama"); list.add(mistake); } }

If we check the /tmp directory, there are five files: 1.xml, 2.xml, 3.xml, 4.xml, 5.xml, each one containing the XML serialized form of our authors.

Playing around
Let's remove mama from the list and iterate over all authors:
package org.codehaus.xstream.examples; public class RemoveMama { public static void main(String[] args) { // prepares the file strategy to directory /tmp StreamStrategy strategy = new FileStreamStrategy(new File("/tmp")); // looks up the list: List list = new XmlArrayList(strategy); // remember the list is still there! the files 1-5.xml are still in /tmp! // the list was persisted! for(Iterator it = list.iterator(); it.hasNext(); ) { Author author = (Author) it.next(); if(author.getName().equals("mama")) { System.out.println("Removing mama..."); it.remove(); } else { System.out.println("Keeping " + author.getName()); } } } }

The result?
Keeping joe walnes Keeping joerg schaible Keeping mauro talevi Keeping guilherme silveira Removing mama...

Going further

From this point on, you can implement different StreamStrategies in order to generate other behaviour using your XmlArrayList collection, or try the other implementations: XmlSet and XmlMap.

JSON Tutorial
Due to XStream's flexible architecture, handling of JSON mappings is as easy as handling of XML documents. All you have to do is to initialize XStream object with an appropriate driver and you are ready to serialize your objects to (and from) JSON. XStream currently delivers two drivers for JSON: The JsonHierarchicalStreamDriver and the JettisonMappedXmlDriver. The first one does not have an additional dependency, but can only be used to write XML, while the second one is based on Jettison and can also deserialize JSON to Java objects again. However, since the JettisonMappedXmlDriver transforms plain XML into JSON, you might get better JSON strings with the JsonHierarchicalStreamDriver, because this driver knows often about the type of the written data and can act properly. One word of warning. JSON is made for an easy data transfer between systems and languages. It's syntax offers much less possibilities to express certain data structures. It supports name/value pairs of primitive data types, arrays and lists and allows to nest them - but that's it! No references, no possibility for meta data (attributes), etc. Therefore do not expect wonders. XStream (and Jettison) try their best, but the procedure to convert any kind of object into JSON is a lossy transformation and especially deserialization will not be possible for any construct. See also FAQ .

Jettison driver
Jettison driver uses Jettison StAX parser to read and write data in JSON format. It is available in XStream from version 1.2.2 and is implemented in com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver class. To successfully use this driver you need to have Jettison project and StAX API in your classpath (see reference for optional dependencies). Alternatively you can download JARs manually. Here are a few simple examples:

Write to JSON with the Jettison-based implementation


The following example:
package com.thoughtworks.xstream.json.test; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; public class WriteTest { public static void main(String[] args) { Product product = new Product("Banana", "123", 23.00); XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.alias("product", Product.class); System.out.println(xstream.toXML(product)); } }

produces the following JSON document:


{"product":{"name":"Banana","id":"123","price":"23.0"}}

As you can see, all standard XStream features (such as aliases) can be used with this driver.

Write to JSON with the self-contained JSON driver


The only difference to the example above is the line with the initialization:
XStream xstream = new XStream(new JsonHierarchicalStreamDriver());

The output is as follows:


{"product": { "name": "Banana", "id": "123", "price": 23.0 }}

While the difference because of line feeds is immediately obvious, you have to note also the value of the price element. This time the driver knew about the numeric value and therefore no quotes were generated. Note that newer Jettison releases than 1.0-RC2 will also try to detect numerical values and omit the quotes. Since Jettison cannot know about the original data type, it has to guess. Hence it will therefore also write the value of the id field as numeric value in future.

Read from JSON


The following code:
package com.thoughtworks.xstream.json.test; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; public class ReadTest { public static void main(String[] args) { String json = "{\"product\":{\"name\":\"Banana\",\"id\":\"123\"" + ",\"price\":\"23.0\"}}"; XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.alias("product", Product.class); Product product = (Product)xstream.fromXML(json); System.out.println(product.getName()); } }

serializes JSON document created with preceding example back to Java object. It prints:
Banana

as a result.

Anda mungkin juga menyukai