Let XStream call the default constructor where possible
XStream is a nice Java library for serializing and deserializing objects. One of it's advantages is that it does not require the deserialized class to have a default constructor. But sometimes this will be a problem. A simple real-life example:
public class Person {
public transient final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private String name = "";
public String getName() {
return name;
}
public void setPerson(Name name) {
pcs.firePropertyChange("name", this.name, this.name = name);
}
}
The PropertyChangeSupport object should be marked transient, otherwise serializing would include the whole object, including its listeners. But sadly, he following code won't work:
XStream xstream = new XStream();
Person p = new Person();
p.setName("Roland");
String serialized = xstream.toXML(p);
// ...
p = xstream.fromXML(serialized);
System.out.println(p.getName()); // prints "Roland"
p.setName("Cybso"); // Throws NullPointerException in Person.setName(Name)
The call to p.setName("Cybso") throws a NullPointerException because pcs has not been initialized.
There are two standard ways to work around this problem. The first is to initialize XStream using a PureJavaReflectionProvider-Instance:
XStream xstream = new XStream(new PureJavaReflectionProvider());
This would force XStream create new objects using Class.newInstance() - and prevents you from (de)serializing classes without default constructor. The other way is to implement a method called readResolve() which will be called after the object has been created:
public class Person {
public transient final PropertyChangeSupport pcs;
private String name;
public Person() {
readResolve();
}
public void readResolve() {
pcs = new PropertyChangeSupport(this);
name = "";
}
public String getName() {
return name;
}
public void setPerson(Name name) {
pcs.firePropertyChange("name", this.name, this.name = name);
}
}
This means to abandon the using of final transient fields and in this special case it enforces the implementation of a getPCS() or delegation methods. So let me suggest another solution: Create a custom converter that feels responsible for all classes having a default constructor. This reduces the final transient problem to classes without a default constructor.
public static class DefaultConstructorConverter extends ReflectionConverter {
public DefaultConstructorConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
super(mapper, reflectionProvider);
}
@Override
public boolean canConvert(Class clazz) {
for (Constructor c : clazz.getConstructors()) {
if (c.getParameterTypes().length == 0) {
return true;
}
}
return false;
}
@Override
protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
try {
Class clazz = Class.forName(reader.getNodeName());
return clazz.newInstance();
} catch (Exception e) {
throw new ConversionException("Could not create instance of class " + reader.getNodeName(), e);
}
}
}
Using this converter the original code will work:
XStream xstream = new XStream();
xstream.registerConverter(new DefaultConstructorConverter(xstream.getMapper(), xstream.getReflectionProvider()));
Person p = new Person();
p.setName("Roland");
String serialized = xstream.toXML(p);
//...
p = xstream.fromXML(serialized);
System.out.println(p.getName()); // prints "Roland"
p.setName("Cybso"); // No exception thrown!
System.out.println(p.getName()); // prints "Cybso"
Happy hacking ;)