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 create a simple converter capable of:

  1. telling its capable of converting Person's
  2. translating a Person instance in XML
  3. translate XML into a new Person

We begin creating the PersonConverter class and implementing the Converter interface:

package com.thoughtworks.xstream.examples;

import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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 com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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.AbstractSingleValueConverter;

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 com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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 java.text.DateFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale; import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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. creates a calendar (current date)
  2. creates the XStream object
  3. registers the converter with a Brazilian Portuguese locale
  4. 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).format(
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;
private char gender; 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;
} public char getGender() {
return gender;
} public void setGenderMale() {
this.gender = 'm';
} public void setGenderFemale() {
this.gender = 'f';
} }

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 and add an own attribute for the gender.
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 com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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.getGender() != '\0') {
writer.addAttribute("gender", Character.toString(birthday.getGender()));
}
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();
String gender = reader.getAttribute("gender");
if (gender != null) {
if (gender.length() > 0) {
if (gender.char(0) == 'f') {
birthday.setGenderFemale();
} else if (gender.char(0) == 'm') {
birthday.setFemale();
} else {
throw new ConversionException("Invalid gender value: " + gender);
}
} else {
throw new ConversionException("Empty string is invalid gender value");
}
}
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;
} }

The unmarshal method ensures the valid value for the gender by throwing a ConversionException for invalid entries.

Note, that attributes will always have to be written and read first. You work on a stream and accessing the value of a tag or its members will close the surrounding tag (that is still active when the method is called).

If the implementation of Birthday ensures, that none of its fields could hold a null value and gender contains a valid 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 com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import 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) {
writer.addAttribute("gender", Character.toString(birthday.getGender()));
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();
if (reader.getAttribute("gender").charAt(0) == 'm') {
birthday.setGenderMale();
} else {
birthday.setGenderFemale();
}
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;
} }

Converter Tutorial的更多相关文章

  1. Writing device drivers in Linux: A brief tutorial

    “Do you pine for the nice days of Minix-1.1, when men were men and wrote their own device drivers?”  ...

  2. Spring-MVC配置Gson做为Message Converter解析Json

    Spring-MVC配置Gson做为Message Converter解析Json 在学习Spring的时候看到可以使用@RequestBody 和@ResponseBody注解来是的Spring自动 ...

  3. Digital Adjustment of DC-DC Converter Output Voltage in Portable Applications

    http://pdfserv.maximintegrated.com/en/an/AN818.pdf http://www.maximintegrated.com/app-notes/index.mv ...

  4. [翻译+山寨]Hangfire Highlighter Tutorial

    前言 Hangfire是一个开源且商业免费使用的工具函数库.可以让你非常容易地在ASP.NET应用(也可以不在ASP.NET应用)中执行多种类型的后台任务,而无需自行定制开发和管理基于Windows ...

  5. Django 1.7 Tutorial 学习笔记

    官方教程在这里 : Here 写在前面的废话:)) 以前学习新东西,第一想到的是找本入门教程,按照书上做一遍.现在看了各种网上的入门教程后,我觉得还是看官方Tutorial靠谱.书的弊端一说一大推 本 ...

  6. 解决springmvc报No converter found for return value of type: class java.util.ArrayList问题

    一.背景 最近闲来无事,想自己搭建一套Spring+SpringMVC+Mybatis+Mysql的环境(搭建步骤会在以后博客中给出),结果运行程序时,适用@ResponseBody注解进行返回Lis ...

  7. thrift 服务端linux C ++ 与客户端 windows python 环境配置(thrift 自带tutorial为例)

    关于Thrift文档化的确是做的不好.摸索了很久才终于把跨linux与windows跨C++与python语言的配置成功完成.以下是步骤: 1)                 Linux下环境配置 ...

  8. Hive Tutorial(上)(Hive 入门指导)

    用户指导 Hive 指导 Hive指导 概念 Hive是什么 Hive不是什么 获得和开始 数据单元 类型系统 内置操作符和方法 语言性能 用法和例子(在<下>里面) 概念 Hive是什么 ...

  9. Home / Python MySQL Tutorial / Calling MySQL Stored Procedures in Python Calling MySQL Stored Procedures in Python

    f you are not familiar with MySQL stored procedures or want to review it as a refresher, you can fol ...

  10. spring类型自动转换——@InitBinder和Converter

    spring有2种类型转换器,一种是propertyEditor,一种是Converter.虽然都是类型转换,但是还是有细微差别. 所以这里以一个例子的形式来分析一下这2种类型转换的使用场景和差别. ...

随机推荐

  1. OData – Get Started 搭建单侧环境

    前言 之前写过 EF Core – Get Started 搭建单侧环境, 这篇补上一个 WebApi + EF Core + OData 创建项目 dotnet new webapi -o EfCo ...

  2. C# – 6.0, 7.0, 8.0, 9.0 总结

    前言 C# 这几年改了好几个版本, 多了许多语法糖,还带有 JavaScript / TypeScript 的味道了. 我觉得随着 blazor 的发展 (想取代前端开发 ?) 那 C# 必然需要更多 ...

  3. Identity – 冷知识

    RoleManager, RoleStore, EF Core 关系 RoleManager 可以理解为一个上层 service, 是让我们操作 Role 的. 比如 create, update, ...

  4. Maven高级——分模块开发与设计

    分模块开发的意义 将原始模块按照功能拆分成若干个子模块,方便模块间的相互调用,接口共享 分模块开发 创建Maven工程 书写模块代码 注意:分模块开发需要先针对模块功能进行设计,再进行编码.不会先将工 ...

  5. 如何创建免费版本的ABP分离模块?

    如何创建免费版本的ABP分离模块? 由于ABP最近官方大改革,我们打开ABP.IO 官方会发现通过Cli创建模板的时候不能创建Trered类型的了 就是创建一个分层的解决方案,其中Web和Http A ...

  6. 数据库运维实操优质文章分享(含Oracle、MySQL等) | 2023年4月刊

    本文为大家整理了墨天轮数据社区2023年4月发布的优质技术文章,主题涵盖Oracle.MySQL.PostgreSQL等数据库的基础安装配置.故障处理.性能优化等日常实践操作,以及概念梳理.常用脚本. ...

  7. 标准库之 datetime和time 模块

    一.time 模块 time模块是Python标准库中最基础.最常用的模块之一.它提供了各种处理时间的方法和函数,如获取当前时间.格式化时间.计算时间差等.time模块大部分函数的底层实现是 C 语言 ...

  8. These dependencies were not found: * core-js/modules/es.array.push.js in ./node_modules/@babel/runtime/helpers/esm/regeneratorRuntime.js

    yarn add core-js  安装core-js包 : 出现这个问题的原因:因为vue-admin-template的package.json里没写这个包core-js,然后再咱们yarn装包的 ...

  9. 现代化 React UI 库:Material-UI 详解!

    随着 React 在前端开发中的流行,越来越多的 UI 框架和库开始涌现,以帮助开发者更高效地构建现代化.响应式的用户界面.其中,Material-UI 是基于 Google Material Des ...

  10. Vite打包碎片化,如何化解?

    背景 我们在使用 Vite 进行打包时,经常会遇到这个问题:随着业务的展开,版本迭代,页面越来越多,第三方依赖也越来越多,打出来的包也越来越大.如果把页面都进行动态导入,那么凡是几个页面共用的文件都会 ...