/*
* Copyright (C) 2004, 2005 Joe Walnes.
* Copyright (C) 2006, 2007 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 23. August 2004 by Joe Walnes
*/
package com.thoughtworks.acceptance;
import com.thoughtworks.acceptance.objects.Hardware;
import com.thoughtworks.acceptance.objects.Software;
import com.thoughtworks.acceptance.objects.StandardObject;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.io.Serializable;
public class CustomSerializationTest extends AbstractAcceptanceTest {
public static class ObjectWithCustomSerialization extends StandardObject implements Serializable {
private int a;
private transient int b;
private transient String c;
private transient Object d;
private transient Software e;
public ObjectWithCustomSerialization() {
}
public ObjectWithCustomSerialization(int a, int b, String c, Software e) {
this.a = a;
this.b = b;
this.c = c;
this.e = e;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
b = in.readInt();
in.defaultReadObject();
c = (String) in.readObject();
d = in.readObject();
e = (Software) in.readObject();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(b);
out.defaultWriteObject();
out.writeObject(c);
out.writeObject(d);
out.writeObject(e);
}
}
public void testWritesCustomFieldsToStream() {
ObjectWithCustomSerialization obj = new ObjectWithCustomSerialization(1, 2, "hello", new Software("tw", "xs"));
xstream.alias("custom", ObjectWithCustomSerialization.class);
xstream.alias("software", Software.class);
String expectedXml = ""
+ "<custom serialization=\"custom\">\n"
+ " <custom>\n"
+ " <int>2</int>\n"
+ " <default>\n"
+ " <a>1</a>\n"
+ " </default>\n"
+ " <string>hello</string>\n"
+ " <null/>\n"
+ " <software>\n"
+ " <vendor>tw</vendor>\n"
+ " <name>xs</name>\n"
+ " </software>\n"
+ " </custom>\n"
+ "</custom>";
assertBothWays(obj, expectedXml);
}
public static class Parent extends StandardObject implements Serializable {
private transient int parentA;
private int parentB;
private transient int parentC;
public Parent() {
}
public Parent(int parentA, int parentB, int parentC) {
this.parentA = parentA;
this.parentB = parentB;
this.parentC = parentC;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
parentA = in.readInt();
in.defaultReadObject();
parentC = in.readInt();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(parentA);
out.defaultWriteObject();
out.writeInt(parentC);
}
}
public static class Child extends Parent {
private transient int childA;
private int childB;
private transient int childC;
public Child() {
}
public Child(int parentA, int parentB, int parentC, int childA, int childB, int childC) {
super(parentA, parentB, parentC);
this.childA = childA;
this.childB = childB;
this.childC = childC;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
childA = in.readInt();
in.defaultReadObject();
childC = in.readInt();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeInt(childA);
out.defaultWriteObject();
out.writeInt(childC);
}
}
public void testIncludesCompleteClassHierarchyWhenParentAndChildHaveSerializationMethods() {
Child child = new Child(1, 2, 3, 10, 20, 30);
xstream.alias("child", Child.class);
xstream.alias("parent", Parent.class);
String expectedXml = ""
+ "<child serialization=\"custom\">\n"
+ " <parent>\n"
+ " <int>1</int>\n"
+ " <default>\n"
+ " <parentB>2</parentB>\n"
+ " </default>\n"
+ " <int>3</int>\n"
+ " </parent>\n"
+ " <child>\n"
+ " <int>10</int>\n"
+ " <default>\n"
+ " <childB>20</childB>\n"
+ " </default>\n"
+ " <int>30</int>\n"
+ " </child>\n"
+ "</child>";
assertBothWays(child, expectedXml);
}
public static class Child2 extends Parent {
private int childA;
public Child2(int parentA, int parentB, int parentC, int childA) {
super(parentA, parentB, parentC);
this.childA = childA;
}
}
public void testIncludesCompleteClassHierarchyWhenOnlyParentHasSerializationMethods() {
Child2 child = new Child2(1, 2, 3, 20);
xstream.alias("child2", Child2.class);
xstream.alias("parent", Parent.class);
String expectedXml = ""
+ "<child2 serialization=\"custom\">\n"
+ " <parent>\n"
+ " <int>1</int>\n"
+ " <default>\n"
+ " <parentB>2</parentB>\n"
+ " </default>\n"
+ " <int>3</int>\n"
+ " </parent>\n"
+ " <child2>\n"
+ " <default>\n"
+ " <childA>20</childA>\n"
+ " </default>\n"
+ " </child2>\n"
+ "</child2>";
assertBothWays(child, expectedXml);
}
static class MyDate extends java.util.Date {
public MyDate(int time) {
super(time);
}
}
static class MyHashtable extends java.util.Hashtable {
private String name;
public MyHashtable(String name) {
this.name = name;
}
public synchronized boolean equals(Object o) {
return super.equals(o) && ((MyHashtable)o).name.equals(name);
}
}
public void testSupportsSubclassesOfClassesThatAlreadyHaveConverters() {
MyDate in = new MyDate(1234567890);
String xml = xstream.toXML(in);
assertObjectsEqual(in, xstream.fromXML(xml));
MyHashtable in2 = new MyHashtable("hi");
in2.put("cheese", "curry");
in2.put("apple", new Integer(3));
String xml2 = xstream.toXML(in2);
assertObjectsEqual(in2, xstream.fromXML(xml2));
}
public static class ObjectWithNamedFields extends StandardObject implements Serializable {
private String name;
private int number;
private Software someSoftware;
private Object polymorphic;
private Object nothing;
private static final ObjectStreamField[] serialPersistentFields = {
new ObjectStreamField("theName", String.class),
new ObjectStreamField("theNumber", int.class),
new ObjectStreamField("theSoftware", Software.class),
new ObjectStreamField("thePolymorphic", Object.class),
new ObjectStreamField("theNothing", Object.class)
};
private void writeObject(ObjectOutputStream out) throws IOException {
// don't call defaultWriteObject()
ObjectOutputStream.PutField fields = out.putFields();
fields.put("theName", name);
fields.put("theNumber", number);
fields.put("theSoftware", someSoftware);
fields.put("thePolymorphic", polymorphic);
fields.put("theNothing", nothing);
out.writeFields();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// don't call defaultReadObject()
ObjectInputStream.GetField fields = in.readFields();
name = (String) fields.get("theName", "unknown");
number = fields.get("theNumber", -1);
someSoftware = (Software) fields.get("theSoftware", null);
polymorphic = fields.get("thePolymorphic", null);
nothing = fields.get("theNothing", null);
}
}
public void testAllowsNamedFields() {
ObjectWithNamedFields obj = new ObjectWithNamedFields();
obj.name = "Joe";
obj.number = 99;
obj.someSoftware = new Software("tw", "xs");
obj.polymorphic = new Hardware("small", "ipod");
obj.nothing = null;
xstream.alias("with-named-fields", ObjectWithNamedFields.class);
xstream.alias("software", Software.class);
String expectedXml = ""
+ "<with-named-fields serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <default>\n"
+ " <theName>Joe</theName>\n"
+ " <theNumber>99</theNumber>\n"
+ " <theSoftware>\n"
+ " <vendor>tw</vendor>\n"
+ " <name>xs</name>\n"
+ " </theSoftware>\n"
+ " <thePolymorphic class=\"com.thoughtworks.acceptance.objects.Hardware\">\n"
+ " <arch>small</arch>\n"
+ " <name>ipod</name>\n"
+ " </thePolymorphic>\n"
+ " </default>\n"
+ " </with-named-fields>\n"
+ "</with-named-fields>";
assertBothWays(obj, expectedXml);
}
public void testUsesDefaultIfNamedFieldNotFound() {
xstream.alias("with-named-fields", ObjectWithNamedFields.class);
xstream.alias("software", Software.class);
String inputXml = ""
+ "<with-named-fields serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <default>\n"
+ " <theSoftware>\n"
+ " <vendor>tw</vendor>\n"
+ " <name>xs</name>\n"
+ " </theSoftware>\n"
+ " <thePolymorphic class=\"com.thoughtworks.acceptance.objects.Hardware\">\n"
+ " <arch>small</arch>\n"
+ " <name>ipod</name>\n"
+ " </thePolymorphic>\n"
+ " </default>\n"
+ " </with-named-fields>\n"
+ "</with-named-fields>";
ObjectWithNamedFields result = (ObjectWithNamedFields) xstream.fromXML(inputXml);
assertEquals(-1, result.number);
assertEquals("unknown", result.name);
assertEquals(new Software("tw", "xs"), result.someSoftware);
}
public void testCustomStreamWithNestedCustomStream() {
ObjectWithNamedFields outer = new ObjectWithNamedFields();
outer.name = "Joe";
outer.someSoftware = new Software("tw", "xs");
outer.nothing = null;
ObjectWithNamedFields inner = new ObjectWithNamedFields();
inner.name = "Thing";
outer.polymorphic = inner;
xstream.alias("with-named-fields", ObjectWithNamedFields.class);
xstream.alias("software", Software.class);
String expectedXml = ""
+ "<with-named-fields serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <default>\n"
+ " <theName>Joe</theName>\n"
+ " <theNumber>0</theNumber>\n"
+ " <theSoftware>\n"
+ " <vendor>tw</vendor>\n"
+ " <name>xs</name>\n"
+ " </theSoftware>\n"
+ " <thePolymorphic class=\"with-named-fields\" serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <default>\n"
+ " <theName>Thing</theName>\n"
+ " <theNumber>0</theNumber>\n"
+ " </default>\n"
+ " </with-named-fields>\n"
+ " </thePolymorphic>\n"
+ " </default>\n"
+ " </with-named-fields>\n"
+ "</with-named-fields>";
assertBothWays(outer, expectedXml);
}
public static class NoDefaultFields extends StandardObject implements Serializable {
private transient int something;
public NoDefaultFields() {
}
public NoDefaultFields(int something) {
this.something = something;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
something = in.readInt();
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(something);
}
}
public void testObjectWithCallToDefaultWriteButNoDefaultFields() {
xstream.alias("x", NoDefaultFields.class);
String expectedXml = ""
+ "<x serialization=\"custom\">\n"
+ " <x>\n"
+ " <default/>\n"
+ " <int>77</int>\n"
+ " </x>\n"
+ "</x>";
assertBothWays(new NoDefaultFields(77), expectedXml);
}
public void testMaintainsBackwardsCompatabilityWithXStream1_1_0FieldFormat() {
ObjectWithNamedFields outer = new ObjectWithNamedFields();
outer.name = "Joe";
outer.someSoftware = new Software("tw", "xs");
outer.nothing = null;
ObjectWithNamedFields inner = new ObjectWithNamedFields();
inner.name = "Thing";
outer.polymorphic = inner;
xstream.alias("with-named-fields", ObjectWithNamedFields.class);
xstream.alias("software", Software.class);
String oldFormatOfXml = ""
+ "<with-named-fields serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <fields>\n"
+ " <field name=\"theName\" class=\"string\">Joe</field>\n"
+ " <field name=\"theNumber\" class=\"int\">0</field>\n"
+ " <field name=\"theSoftware\" class=\"software\">\n"
+ " <vendor>tw</vendor>\n"
+ " <name>xs</name>\n"
+ " </field>\n"
+ " <field name=\"thePolymorphic\" class=\"with-named-fields\" serialization=\"custom\">\n"
+ " <with-named-fields>\n"
+ " <fields>\n"
+ " <field name=\"theName\" class=\"string\">Thing</field>\n"
+ " <field name=\"theNumber\" class=\"int\">0</field>\n"
+ " </fields>\n"
+ " </with-named-fields>\n"
+ " </field>\n"
+ " </fields>\n"
+ " </with-named-fields>\n"
+ "</with-named-fields>";
assertEquals(outer, xstream.fromXML(oldFormatOfXml));
}
public static class ObjectWithNamedThatMatchRealFields extends StandardObject implements Serializable {
private String name;
private int number;
private void writeObject(ObjectOutputStream out) throws IOException {
ObjectOutputStream.PutField fields = out.putFields();
fields.put("name", name.toUpperCase());
fields.put("number", number * 100);
out.writeFields();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = in.readFields();
name = ((String) fields.get("name", "unknown")).toLowerCase();
number = fields.get("number", 10000) / 100;
}
}
public void testSupportsWritingFieldsForObjectsThatDoNotExplicitlyDefineThem() {
xstream.alias("an-object", ObjectWithNamedThatMatchRealFields.class);
ObjectWithNamedThatMatchRealFields input = new ObjectWithNamedThatMatchRealFields();
input.name = "a name";
input.number = 5;
String expectedXml = ""
+ "<an-object serialization=\"custom\">\n"
+ " <an-object>\n"
+ " <default>\n"
+ " <name>A NAME</name>\n"
+ " <number>500</number>\n"
+ " </default>\n"
+ " </an-object>\n"
+ "</an-object>";
assertBothWays(input, expectedXml);
}
public static class ObjectThatReadsCustomFieldsButDoesNotWriteThem extends StandardObject implements Serializable {
private String name;
private int number;
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField fields = in.readFields();
name = ((String) fields.get("name", "unknown"));
number = fields.get("number", 10000);
}
}
public void testSupportsGetFieldsWithoutPutFields() {
xstream.alias("an-object", ObjectThatReadsCustomFieldsButDoesNotWriteThem.class);
ObjectThatReadsCustomFieldsButDoesNotWriteThem input = new ObjectThatReadsCustomFieldsButDoesNotWriteThem();
input.name = "a name";
input.number = 5;
String expectedXml = ""
+ "<an-object serialization=\"custom\">\n"
+ " <an-object>\n"
+ " <default>\n"
+ " <number>5</number>\n"
+ " <name>a name</name>\n"
+ " </default>\n"
+ " </an-object>\n"
+ "</an-object>";
assertBothWays(input, expectedXml);
}
}