001: /*
002: * Copyright (C) 2004, 2005 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 23. August 2004 by Joe Walnes
011: */
012: package com.thoughtworks.acceptance;
013:
014: import com.thoughtworks.acceptance.objects.Hardware;
015: import com.thoughtworks.acceptance.objects.Software;
016: import com.thoughtworks.acceptance.objects.StandardObject;
017:
018: import java.io.IOException;
019: import java.io.ObjectInputStream;
020: import java.io.ObjectOutputStream;
021: import java.io.ObjectStreamField;
022: import java.io.Serializable;
023:
024: public class CustomSerializationTest extends AbstractAcceptanceTest {
025:
026: public static class ObjectWithCustomSerialization extends
027: StandardObject implements Serializable {
028:
029: private int a;
030: private transient int b;
031: private transient String c;
032: private transient Object d;
033: private transient Software e;
034:
035: public ObjectWithCustomSerialization() {
036: }
037:
038: public ObjectWithCustomSerialization(int a, int b, String c,
039: Software e) {
040: this .a = a;
041: this .b = b;
042: this .c = c;
043: this .e = e;
044: }
045:
046: private void readObject(ObjectInputStream in)
047: throws IOException, ClassNotFoundException {
048: b = in.readInt();
049: in.defaultReadObject();
050: c = (String) in.readObject();
051: d = in.readObject();
052: e = (Software) in.readObject();
053: }
054:
055: private void writeObject(ObjectOutputStream out)
056: throws IOException {
057: out.writeInt(b);
058: out.defaultWriteObject();
059: out.writeObject(c);
060: out.writeObject(d);
061: out.writeObject(e);
062: }
063:
064: }
065:
066: public void testWritesCustomFieldsToStream() {
067: ObjectWithCustomSerialization obj = new ObjectWithCustomSerialization(
068: 1, 2, "hello", new Software("tw", "xs"));
069: xstream.alias("custom", ObjectWithCustomSerialization.class);
070: xstream.alias("software", Software.class);
071:
072: String expectedXml = "" + "<custom serialization=\"custom\">\n"
073: + " <custom>\n" + " <int>2</int>\n"
074: + " <default>\n" + " <a>1</a>\n"
075: + " </default>\n" + " <string>hello</string>\n"
076: + " <null/>\n" + " <software>\n"
077: + " <vendor>tw</vendor>\n"
078: + " <name>xs</name>\n" + " </software>\n"
079: + " </custom>\n" + "</custom>";
080:
081: assertBothWays(obj, expectedXml);
082: }
083:
084: public static class Parent extends StandardObject implements
085: Serializable {
086:
087: private transient int parentA;
088: private int parentB;
089: private transient int parentC;
090:
091: public Parent() {
092: }
093:
094: public Parent(int parentA, int parentB, int parentC) {
095: this .parentA = parentA;
096: this .parentB = parentB;
097: this .parentC = parentC;
098: }
099:
100: private void readObject(ObjectInputStream in)
101: throws IOException, ClassNotFoundException {
102: parentA = in.readInt();
103: in.defaultReadObject();
104: parentC = in.readInt();
105: }
106:
107: private void writeObject(ObjectOutputStream out)
108: throws IOException {
109: out.writeInt(parentA);
110: out.defaultWriteObject();
111: out.writeInt(parentC);
112: }
113: }
114:
115: public static class Child extends Parent {
116:
117: private transient int childA;
118: private int childB;
119: private transient int childC;
120:
121: public Child() {
122: }
123:
124: public Child(int parentA, int parentB, int parentC, int childA,
125: int childB, int childC) {
126: super (parentA, parentB, parentC);
127: this .childA = childA;
128: this .childB = childB;
129: this .childC = childC;
130: }
131:
132: private void readObject(ObjectInputStream in)
133: throws IOException, ClassNotFoundException {
134: childA = in.readInt();
135: in.defaultReadObject();
136: childC = in.readInt();
137: }
138:
139: private void writeObject(ObjectOutputStream out)
140: throws IOException {
141: out.writeInt(childA);
142: out.defaultWriteObject();
143: out.writeInt(childC);
144: }
145: }
146:
147: public void testIncludesCompleteClassHierarchyWhenParentAndChildHaveSerializationMethods() {
148: Child child = new Child(1, 2, 3, 10, 20, 30);
149: xstream.alias("child", Child.class);
150: xstream.alias("parent", Parent.class);
151:
152: String expectedXml = "" + "<child serialization=\"custom\">\n"
153: + " <parent>\n" + " <int>1</int>\n"
154: + " <default>\n" + " <parentB>2</parentB>\n"
155: + " </default>\n" + " <int>3</int>\n"
156: + " </parent>\n" + " <child>\n"
157: + " <int>10</int>\n" + " <default>\n"
158: + " <childB>20</childB>\n" + " </default>\n"
159: + " <int>30</int>\n" + " </child>\n" + "</child>";
160:
161: assertBothWays(child, expectedXml);
162: }
163:
164: public static class Child2 extends Parent {
165:
166: private int childA;
167:
168: public Child2(int parentA, int parentB, int parentC, int childA) {
169: super (parentA, parentB, parentC);
170: this .childA = childA;
171: }
172:
173: }
174:
175: public void testIncludesCompleteClassHierarchyWhenOnlyParentHasSerializationMethods() {
176: Child2 child = new Child2(1, 2, 3, 20);
177: xstream.alias("child2", Child2.class);
178: xstream.alias("parent", Parent.class);
179:
180: String expectedXml = "" + "<child2 serialization=\"custom\">\n"
181: + " <parent>\n" + " <int>1</int>\n"
182: + " <default>\n" + " <parentB>2</parentB>\n"
183: + " </default>\n" + " <int>3</int>\n"
184: + " </parent>\n" + " <child2>\n" + " <default>\n"
185: + " <childA>20</childA>\n" + " </default>\n"
186: + " </child2>\n" + "</child2>";
187:
188: assertBothWays(child, expectedXml);
189: }
190:
191: static class MyDate extends java.util.Date {
192: public MyDate(int time) {
193: super (time);
194: }
195: }
196:
197: static class MyHashtable extends java.util.Hashtable {
198: private String name;
199:
200: public MyHashtable(String name) {
201: this .name = name;
202: }
203:
204: public synchronized boolean equals(Object o) {
205: return super .equals(o)
206: && ((MyHashtable) o).name.equals(name);
207: }
208: }
209:
210: public void testSupportsSubclassesOfClassesThatAlreadyHaveConverters() {
211: MyDate in = new MyDate(1234567890);
212: String xml = xstream.toXML(in);
213: assertObjectsEqual(in, xstream.fromXML(xml));
214:
215: MyHashtable in2 = new MyHashtable("hi");
216: in2.put("cheese", "curry");
217: in2.put("apple", new Integer(3));
218: String xml2 = xstream.toXML(in2);
219: assertObjectsEqual(in2, xstream.fromXML(xml2));
220: }
221:
222: public static class ObjectWithNamedFields extends StandardObject
223: implements Serializable {
224:
225: private String name;
226: private int number;
227: private Software someSoftware;
228: private Object polymorphic;
229: private Object nothing;
230:
231: private static final ObjectStreamField[] serialPersistentFields = {
232: new ObjectStreamField("theName", String.class),
233: new ObjectStreamField("theNumber", int.class),
234: new ObjectStreamField("theSoftware", Software.class),
235: new ObjectStreamField("thePolymorphic", Object.class),
236: new ObjectStreamField("theNothing", Object.class) };
237:
238: private void writeObject(ObjectOutputStream out)
239: throws IOException {
240: // don't call defaultWriteObject()
241: ObjectOutputStream.PutField fields = out.putFields();
242: fields.put("theName", name);
243: fields.put("theNumber", number);
244: fields.put("theSoftware", someSoftware);
245: fields.put("thePolymorphic", polymorphic);
246: fields.put("theNothing", nothing);
247: out.writeFields();
248: }
249:
250: private void readObject(ObjectInputStream in)
251: throws IOException, ClassNotFoundException {
252: // don't call defaultReadObject()
253: ObjectInputStream.GetField fields = in.readFields();
254: name = (String) fields.get("theName", "unknown");
255: number = fields.get("theNumber", -1);
256: someSoftware = (Software) fields.get("theSoftware", null);
257: polymorphic = fields.get("thePolymorphic", null);
258: nothing = fields.get("theNothing", null);
259: }
260:
261: }
262:
263: public void testAllowsNamedFields() {
264: ObjectWithNamedFields obj = new ObjectWithNamedFields();
265: obj.name = "Joe";
266: obj.number = 99;
267: obj.someSoftware = new Software("tw", "xs");
268: obj.polymorphic = new Hardware("small", "ipod");
269: obj.nothing = null;
270:
271: xstream.alias("with-named-fields", ObjectWithNamedFields.class);
272: xstream.alias("software", Software.class);
273:
274: String expectedXml = ""
275: + "<with-named-fields serialization=\"custom\">\n"
276: + " <with-named-fields>\n"
277: + " <default>\n"
278: + " <theName>Joe</theName>\n"
279: + " <theNumber>99</theNumber>\n"
280: + " <theSoftware>\n"
281: + " <vendor>tw</vendor>\n"
282: + " <name>xs</name>\n"
283: + " </theSoftware>\n"
284: + " <thePolymorphic class=\"com.thoughtworks.acceptance.objects.Hardware\">\n"
285: + " <arch>small</arch>\n"
286: + " <name>ipod</name>\n"
287: + " </thePolymorphic>\n" + " </default>\n"
288: + " </with-named-fields>\n" + "</with-named-fields>";
289:
290: assertBothWays(obj, expectedXml);
291: }
292:
293: public void testUsesDefaultIfNamedFieldNotFound() {
294: xstream.alias("with-named-fields", ObjectWithNamedFields.class);
295: xstream.alias("software", Software.class);
296:
297: String inputXml = ""
298: + "<with-named-fields serialization=\"custom\">\n"
299: + " <with-named-fields>\n"
300: + " <default>\n"
301: + " <theSoftware>\n"
302: + " <vendor>tw</vendor>\n"
303: + " <name>xs</name>\n"
304: + " </theSoftware>\n"
305: + " <thePolymorphic class=\"com.thoughtworks.acceptance.objects.Hardware\">\n"
306: + " <arch>small</arch>\n"
307: + " <name>ipod</name>\n"
308: + " </thePolymorphic>\n" + " </default>\n"
309: + " </with-named-fields>\n" + "</with-named-fields>";
310:
311: ObjectWithNamedFields result = (ObjectWithNamedFields) xstream
312: .fromXML(inputXml);
313: assertEquals(-1, result.number);
314: assertEquals("unknown", result.name);
315: assertEquals(new Software("tw", "xs"), result.someSoftware);
316: }
317:
318: public void testCustomStreamWithNestedCustomStream() {
319: ObjectWithNamedFields outer = new ObjectWithNamedFields();
320: outer.name = "Joe";
321: outer.someSoftware = new Software("tw", "xs");
322: outer.nothing = null;
323:
324: ObjectWithNamedFields inner = new ObjectWithNamedFields();
325: inner.name = "Thing";
326:
327: outer.polymorphic = inner;
328:
329: xstream.alias("with-named-fields", ObjectWithNamedFields.class);
330: xstream.alias("software", Software.class);
331:
332: String expectedXml = ""
333: + "<with-named-fields serialization=\"custom\">\n"
334: + " <with-named-fields>\n"
335: + " <default>\n"
336: + " <theName>Joe</theName>\n"
337: + " <theNumber>0</theNumber>\n"
338: + " <theSoftware>\n"
339: + " <vendor>tw</vendor>\n"
340: + " <name>xs</name>\n"
341: + " </theSoftware>\n"
342: + " <thePolymorphic class=\"with-named-fields\" serialization=\"custom\">\n"
343: + " <with-named-fields>\n"
344: + " <default>\n"
345: + " <theName>Thing</theName>\n"
346: + " <theNumber>0</theNumber>\n"
347: + " </default>\n"
348: + " </with-named-fields>\n"
349: + " </thePolymorphic>\n" + " </default>\n"
350: + " </with-named-fields>\n" + "</with-named-fields>";
351:
352: assertBothWays(outer, expectedXml);
353: }
354:
355: public static class NoDefaultFields extends StandardObject
356: implements Serializable {
357:
358: private transient int something;
359:
360: public NoDefaultFields() {
361: }
362:
363: public NoDefaultFields(int something) {
364: this .something = something;
365: }
366:
367: private void readObject(ObjectInputStream in)
368: throws IOException, ClassNotFoundException {
369: in.defaultReadObject();
370: something = in.readInt();
371: }
372:
373: private void writeObject(ObjectOutputStream out)
374: throws IOException {
375: out.defaultWriteObject();
376: out.writeInt(something);
377: }
378:
379: }
380:
381: public void testObjectWithCallToDefaultWriteButNoDefaultFields() {
382: xstream.alias("x", NoDefaultFields.class);
383:
384: String expectedXml = "" + "<x serialization=\"custom\">\n"
385: + " <x>\n" + " <default/>\n"
386: + " <int>77</int>\n" + " </x>\n" + "</x>";
387: assertBothWays(new NoDefaultFields(77), expectedXml);
388: }
389:
390: public void testMaintainsBackwardsCompatabilityWithXStream1_1_0FieldFormat() {
391: ObjectWithNamedFields outer = new ObjectWithNamedFields();
392: outer.name = "Joe";
393: outer.someSoftware = new Software("tw", "xs");
394: outer.nothing = null;
395:
396: ObjectWithNamedFields inner = new ObjectWithNamedFields();
397: inner.name = "Thing";
398:
399: outer.polymorphic = inner;
400:
401: xstream.alias("with-named-fields", ObjectWithNamedFields.class);
402: xstream.alias("software", Software.class);
403:
404: String oldFormatOfXml = ""
405: + "<with-named-fields serialization=\"custom\">\n"
406: + " <with-named-fields>\n"
407: + " <fields>\n"
408: + " <field name=\"theName\" class=\"string\">Joe</field>\n"
409: + " <field name=\"theNumber\" class=\"int\">0</field>\n"
410: + " <field name=\"theSoftware\" class=\"software\">\n"
411: + " <vendor>tw</vendor>\n"
412: + " <name>xs</name>\n"
413: + " </field>\n"
414: + " <field name=\"thePolymorphic\" class=\"with-named-fields\" serialization=\"custom\">\n"
415: + " <with-named-fields>\n"
416: + " <fields>\n"
417: + " <field name=\"theName\" class=\"string\">Thing</field>\n"
418: + " <field name=\"theNumber\" class=\"int\">0</field>\n"
419: + " </fields>\n"
420: + " </with-named-fields>\n" + " </field>\n"
421: + " </fields>\n" + " </with-named-fields>\n"
422: + "</with-named-fields>";
423:
424: assertEquals(outer, xstream.fromXML(oldFormatOfXml));
425: }
426:
427: public static class ObjectWithNamedThatMatchRealFields extends
428: StandardObject implements Serializable {
429:
430: private String name;
431: private int number;
432:
433: private void writeObject(ObjectOutputStream out)
434: throws IOException {
435: ObjectOutputStream.PutField fields = out.putFields();
436: fields.put("name", name.toUpperCase());
437: fields.put("number", number * 100);
438: out.writeFields();
439: }
440:
441: private void readObject(ObjectInputStream in)
442: throws IOException, ClassNotFoundException {
443: ObjectInputStream.GetField fields = in.readFields();
444: name = ((String) fields.get("name", "unknown"))
445: .toLowerCase();
446: number = fields.get("number", 10000) / 100;
447: }
448:
449: }
450:
451: public void testSupportsWritingFieldsForObjectsThatDoNotExplicitlyDefineThem() {
452: xstream.alias("an-object",
453: ObjectWithNamedThatMatchRealFields.class);
454:
455: ObjectWithNamedThatMatchRealFields input = new ObjectWithNamedThatMatchRealFields();
456: input.name = "a name";
457: input.number = 5;
458:
459: String expectedXml = ""
460: + "<an-object serialization=\"custom\">\n"
461: + " <an-object>\n" + " <default>\n"
462: + " <name>A NAME</name>\n"
463: + " <number>500</number>\n" + " </default>\n"
464: + " </an-object>\n" + "</an-object>";
465:
466: assertBothWays(input, expectedXml);
467: }
468:
469: public static class ObjectThatReadsCustomFieldsButDoesNotWriteThem
470: extends StandardObject implements Serializable {
471:
472: private String name;
473: private int number;
474:
475: private void writeObject(ObjectOutputStream out)
476: throws IOException {
477: out.defaultWriteObject();
478: }
479:
480: private void readObject(ObjectInputStream in)
481: throws IOException, ClassNotFoundException {
482: ObjectInputStream.GetField fields = in.readFields();
483: name = ((String) fields.get("name", "unknown"));
484: number = fields.get("number", 10000);
485: }
486:
487: }
488:
489: public void testSupportsGetFieldsWithoutPutFields() {
490: xstream.alias("an-object",
491: ObjectThatReadsCustomFieldsButDoesNotWriteThem.class);
492:
493: ObjectThatReadsCustomFieldsButDoesNotWriteThem input = new ObjectThatReadsCustomFieldsButDoesNotWriteThem();
494: input.name = "a name";
495: input.number = 5;
496:
497: String expectedXml = ""
498: + "<an-object serialization=\"custom\">\n"
499: + " <an-object>\n" + " <default>\n"
500: + " <number>5</number>\n"
501: + " <name>a name</name>\n" + " </default>\n"
502: + " </an-object>\n" + "</an-object>";
503:
504: assertBothWays(input, expectedXml);
505: }
506: }
|