Sunday, 7 November 2010

Taming XStream part 3

The final part of this series will detail my attempt to deserialise an arbitary sample XML file into Java objects. I have selected the following RSS feed XML file:


<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title>Sample Channel</title> 
        <link>http://www.playstation.com/manual/psp/rss/</link> 
        <description>Contains sample content</description> 
        <language>en-us</language>
        <image>
            <url>http://www.playstation.com/manual/psp/rss/sample_ch.jpg</url>
            <title>Sample Channel</title>
        </image>
        <copyright>&#xA9; 2005 Sony Computer Entertainment Inc. All rights reserved.</copyright>
        <item>
            <title>HOUSE SAMPLE 1</title> 
            <link>http://www.playstation.com/manual/psp/rss/</link> 
            <description>HOUSE SAMPLE 1 / PSP Mix</description>
            <author>Noburo Masuda</author>
            <pubDate>Tue, 29 Nov 2005 15:00:00 +0900</pubDate> 
            <enclosure url="http://fj00.psp.update.playstation.org/media/HOUSE_SAMPLE_1.mp3" type="audio/mp3" /> 
        </item>
        <item>
            <title>JAZZ FUSION SAMPLE 1</title> 
            <link>http://www.playstation.com/manual/psp/rss/</link> 
            <description>JAZZ FUSION SAMPLE 1 / PSP Mix</description>
            <author>Noburo Masuda</author>
            <pubDate>Tue, 29 Nov 2005 14:00:00 +0900</pubDate>
            <enclosure url="http://fj00.psp.update.playstation.org/media/JAZZ_FUSION_SAMPLE_1.mp3" type="audio/mp3" /> 
        </item>
        <item>
            <title>Sample Video Clip</title> 
            <link>http://www.playstation.com/manual/psp/rss/</link> 
            <description>Sample Clip for RSS ch</description>
            <author>Sony Computer Entertainment Inc.</author>
            <pubDate>Thu, 27 Jul 2006 15:00:00 +0900</pubDate>
            <enclosure url="http://fj00.psp.update.playstation.org/media/SampleVideoClip.mp4" type="video/mp4" /> 
    <media:thumbnail url="http://fj00.psp.update.playstation.org/media/SampleVideoClip_tn.jpg" width="60" height="45"/>
        </item>
    </channel>
</rss>

This example includes collections of sub types, attributes and fields. We will need to create a class hierarchy which looks like RSS -> Channel -> Item. Then complete the appropriate field and attribute mapping as required. The following is the structure I have decided on:


private static class RSS {
private Channel channel;
public RSS(Channel channel) {
this.channel = channel;
}
}

private static class Channel {
String title;
String link;
String description;
String language;
Image image;
String copyright;
List<Item> items;
}

private static class Image {
String url;
String title;
}

private static class Item {
String title;
String link;
String description;
String author;
String pubDate;
Enclosure enclosure;
Thumbnail thumbnail;
}

private static class Enclosure {
String url;
String type;
}

private static class Thumbnail {
String url;
int width;
int height;
}

The following is the completed code listing which correctly deserialises the XML:


package xstream;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.util.List;

public class Three {
public static void main(String [] args) {
XStream xstream = new XStream(new DomDriver());

// RSS
xstream.alias("rss", RSS.class);

// Channel
xstream.alias("channel", Channel.class);
xstream.addImplicitCollection(Channel.class, "items", "item", Item.class);

// Item
xstream.alias("enclosure", Enclosure.class);
xstream.alias("media:thumbnail", Thumbnail.class);
xstream.aliasField("media:thumbnail", Item.class, "thumbnail");

// Enclosure
xstream.aliasAttribute(Enclosure.class, "type", "type");
xstream.aliasAttribute(Enclosure.class, "url", "url");

// Thumbnail
xstream.aliasAttribute(Thumbnail.class, "url", "url");
xstream.aliasAttribute(Thumbnail.class, "width", "width");
xstream.aliasAttribute(Thumbnail.class, "height", "height");


RSS data = (RSS) xstream.fromXML(
One.class.getResourceAsStream("sample_ch.xml"));

System.out.println(data.toString());
}

private static class RSS {
private Channel channel;
public RSS(Channel channel) {
this.channel = channel;
}

@Override
public String toString() {
return channel.toString();
}
}

private static class Channel {
String title;
String link;
String description;
String language;
Image image;
String copyright;
List<Item> items;

@Override
public String toString() {
String r = "";
r += "title: " + title + "\n";
r += "description: " + description + "\n";
r += "image: " + image.toString() + "\n";
r += "copyright: " + copyright + "\n";
for (Item item : items) {
r += "\n";
r += item.toString() + "\n";
}
return r;
}
}

private static class Image {
String url;
String title;

@Override
public String toString() {
return title + " (" + url + ")";
}
}

private static class Item {
String title;
String link;
String description;
String author;
String pubDate;
Enclosure enclosure;
Thumbnail thumbnail;

@Override
public String toString() {
String r = "";
r += "title: " + title + "\n";
r += "description: " + description + "\n";
r += "link: " + link + "\n";
r += "pubDate: " + pubDate + "\n";

if (enclosure != null) {
r += enclosure.toString() + "\n";
}

if (thumbnail != null) {
r += thumbnail.toString() + "\n";
}

return r;
}
}

private static class Enclosure {
String url;
String type;

@Override
public String toString() {
return url + " (" + type + ")";
}
}

private static class Thumbnail {
String url;
int width;
int height;

@Override
public String toString() {
return url + " (" + width + "x" + height + ")";
}
}
}

Taming XStream part 2

Continuing on from the previous article, we will now make some adjustments to the XML and demonstrate the changes required to deserialise it into a Java obect.


<?xml version="1.0" encoding="UTF-8"?>
<xml>
    <ips state="normal">
        <ip>192.168.*.1</ip>
        <ip>127.0.0.1</ip>
        <ip>10.0.*</ip>
    </ips>
    <ips state="active">
        <ip>126.111.*</ip>
        <ip>127.0.0.1</ip>
        <ip>99.99.1.1</ip>
    </ips>
</xml>

Here we have changed the structure by saying that the ips tag is actually a list item and contains an attribute. How does our Java object change to represent this structual change?


private class XML {
private List<IPData> ips;
public XML(List<IPData> ips) {

}
}

private class IPData {
String state;
List<String> list = new ArrayList<String>();
}

// The following describes this to XStream
xstream.alias("xml", XML.class);
xstream.addImplicitCollection(XML.class, "ips", IPData.class);
xstream.alias("ips", IPData.class);
xstream.aliasAttribute(IPData.class, "state", "state");
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

In this case, we have changed the constructor definition to a list of IPData instances. This is enough to get XStream to deserialise the ips tag correctly. Also we have added the attribute definiation to convert the attribute of ips to a field of the IPData class.


The complete code listing for this is below:


package xstream;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.util.ArrayList;
import java.util.List;

public class Two {
public static void main(String[] args) {
XStream xstream = new XStream(new DomDriver());

xstream.alias("xml", XML.class);
xstream.addImplicitCollection(XML.class, "ips", IPData.class);
xstream.alias("ips", IPData.class);
xstream.aliasAttribute(IPData.class, "state", "state");
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

XML data = (XML) xstream.fromXML(
Two.class.getResourceAsStream("ip-ranges-two.xml"));

for (IPData d : data.ips) {
System.out.println("State: " + d.state);
for (String s : d.list) {
System.out.println(s);
}
}
}

private class XML {
private List<IPData> ips;
public XML(List<IPData> ips) {

}
}

private class IPData {
String state;
List<String> list = new ArrayList<String>();
}
}

Tuesday, 2 November 2010

Taming XStream part 1

I have used XStream for a while for the general purpose duty of Java object serialisation to XML. However I have not attempted to deserialise a non XStream generated piece of XML into Java objects before.


Whilst attempting to do this I realised that it is not as simple as it first seems. After searching the few tutorials there are on the website it became clear there is little documentation to cover this process.


For this guide, I assume you have read Two Minute Tutorial and Alias Tutorial.


We will start with the following XML we need to deserialise and work with:


<?xml version="1.0" encoding="UTF-8"?>
<xml>
    <ips>
        <ip>192.168.*.1</ip>
        <ip>127.0.0.1</ip>
        <ip>10.0.*</ip>
    </ips>
</xml>

They key concept with XStream is that each layer of tags is an encapsulated level in the object hierarchy. To use the above example, there must be an object which maps to xml, and an encapsulated object within xml which maps to ips.


So, we will create a suitable data object. I have also included the xstream statements that map to this data object:


private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

// This maps as follows
xstream.alias("xml", XML.class);

With XStream, when we wish to define sub tag we have two choices. We can either say that the class needs the sub tag as a constructor argument. Or we can use the aliasField function to map the sub tag to a named field within the class as below:


private class XML {
private IPData ips;
}

// This maps as follows
xstream.alias("xml", XML.class);
xstream.aliasField("ips", XML.class, "ips");

Regardless of the approach we use. The next problem to tackle is the next level down. The ips tag contains three ip tags which contain string data. The important thing here is that the mapping we create describes a collection. We can do this with the addImplicitCollection function:


private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

private class IPData {
List<String> list = new ArrayList<String>();
}

// With the following mapping
xstream.alias("xml", XML.class);
xstream.alias("ips", IPData.class);
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

In this case, we have defined that the IPData class maps to the ips tag. Then we have defined that there is an implicit collection which maps to the field list and contains Strings.


This works rather nicely as the following completed code segment demonstrates:


package xstream;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import java.util.ArrayList;
import java.util.List;

public class One {
public static void main(String[] args) {
XStream xstream = new XStream(new DomDriver());

xstream.alias("xml", XML.class);
xstream.alias("ips", IPData.class);
xstream.addImplicitCollection(IPData.class, "list", "ip", String.class);

XML data = (XML) xstream.fromXML(
One.class.getResourceAsStream("ip-ranges-one.xml"));

for (String s : data.ips.list) {
System.out.println(s);
}
}

private class XML {
private IPData ips;
public XML(IPData ips) {
this.ips = ips;
}
}

private class IPData {
List<String> list = new ArrayList<String>();
}
}