1 package com.thoughtworks.acceptance; 2 3 import com.thoughtworks.acceptance.objects.Software; 4 import com.thoughtworks.acceptance.objects.Hardware; 5 6 import java.io.IOException ; 7 import java.io.ObjectInputStream ; 8 import java.io.ObjectOutputStream ; 9 import java.io.Serializable ; 10 import java.io.ObjectStreamField ; 11 import java.util.Hashtable ; 12 import java.util.Vector ; 13 14 public class CustomSerializationTest extends AbstractAcceptanceTest { 15 16 public static class ObjectWithCustomSerialization extends StandardObject implements Serializable { 17 18 private int a; 19 private transient int b; 20 private transient String c; 21 private transient Object d; 22 private transient Software e; 23 24 public ObjectWithCustomSerialization() { 25 } 26 27 public ObjectWithCustomSerialization(int a, int b, String c, Software e) { 28 this.a = a; 29 this.b = b; 30 this.c = c; 31 this.e = e; 32 } 33 34 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 35 b = in.readInt(); 36 in.defaultReadObject(); 37 c = (String ) in.readObject(); 38 d = in.readObject(); 39 e = (Software) in.readObject(); 40 } 41 42 private void writeObject(ObjectOutputStream out) throws IOException { 43 out.writeInt(b); 44 out.defaultWriteObject(); 45 out.writeObject(c); 46 out.writeObject(d); 47 out.writeObject(e); 48 } 49 50 } 51 52 public void testWritesCustomFieldsToStream() { 53 ObjectWithCustomSerialization obj = new ObjectWithCustomSerialization(1, 2, "hello", new Software("tw", "xs")); 54 xstream.alias("custom", ObjectWithCustomSerialization.class); 55 xstream.alias("software", Software.class); 56 57 String expectedXml = "" 58 + "<custom serialization=\"custom\">\n" 59 + " <custom>\n" 60 + " <int>2</int>\n" 61 + " <default>\n" 62 + " <a>1</a>\n" 63 + " </default>\n" 64 + " <string>hello</string>\n" 65 + " <null/>\n" 66 + " <software>\n" 67 + " <vendor>tw</vendor>\n" 68 + " <name>xs</name>\n" 69 + " </software>\n" 70 + " </custom>\n" 71 + "</custom>"; 72 73 assertBothWays(obj, expectedXml); 74 } 75 76 public static class Parent extends StandardObject implements Serializable { 77 78 private transient int parentA; 79 private int parentB; 80 private transient int parentC; 81 82 public Parent() { 83 } 84 85 public Parent(int parentA, int parentB, int parentC) { 86 this.parentA = parentA; 87 this.parentB = parentB; 88 this.parentC = parentC; 89 } 90 91 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 92 parentA = in.readInt(); 93 in.defaultReadObject(); 94 parentC = in.readInt(); 95 } 96 97 private void writeObject(ObjectOutputStream out) throws IOException { 98 out.writeInt(parentA); 99 out.defaultWriteObject(); 100 out.writeInt(parentC); 101 } 102 } 103 104 public static class Child extends Parent { 105 106 private transient int childA; 107 private int childB; 108 private transient int childC; 109 110 public Child() { 111 } 112 113 public Child(int parentA, int parentB, int parentC, int childA, int childB, int childC) { 114 super(parentA, parentB, parentC); 115 this.childA = childA; 116 this.childB = childB; 117 this.childC = childC; 118 } 119 120 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 121 childA = in.readInt(); 122 in.defaultReadObject(); 123 childC = in.readInt(); 124 } 125 126 private void writeObject(ObjectOutputStream out) throws IOException { 127 out.writeInt(childA); 128 out.defaultWriteObject(); 129 out.writeInt(childC); 130 } 131 } 132 133 public void testIncludesCompleteClassHeirarchy() { 134 Child child = new Child(1, 2, 3, 10, 20, 30); 135 xstream.alias("child", Child.class); 136 xstream.alias("parent", Parent.class); 137 138 String expectedXml = "" 139 + "<child serialization=\"custom\">\n" 140 + " <parent>\n" 141 + " <int>1</int>\n" 142 + " <default>\n" 143 + " <parentB>2</parentB>\n" 144 + " </default>\n" 145 + " <int>3</int>\n" 146 + " </parent>\n" 147 + " <child>\n" 148 + " <int>10</int>\n" 149 + " <default>\n" 150 + " <childB>20</childB>\n" 151 + " </default>\n" 152 + " <int>30</int>\n" 153 + " </child>\n" 154 + "</child>"; 155 156 assertBothWays(child, expectedXml); 157 } 158 159 public static class Child2 extends Parent { 160 161 private int childA; 162 163 public Child2(int parentA, int parentB, int parentC, int childA) { 164 super(parentA, parentB, parentC); 165 this.childA = childA; 166 } 167 168 } 169 170 public void testIncludesCompleteClassHeirarchy2() { 171 Child2 child = new Child2(1, 2, 3, 20); 172 xstream.alias("child2", Child2.class); 173 xstream.alias("parent", Parent.class); 174 175 String expectedXml = "" 176 + "<child2 serialization=\"custom\">\n" 177 + " <parent>\n" 178 + " <int>1</int>\n" 179 + " <default>\n" 180 + " <parentB>2</parentB>\n" 181 + " </default>\n" 182 + " <int>3</int>\n" 183 + " </parent>\n" 184 + " <child2>\n" 185 + " <default>\n" 186 + " <childA>20</childA>\n" 187 + " </default>\n" 188 + " </child2>\n" 189 + "</child2>"; 190 191 assertBothWays(child, expectedXml); 192 } 193 194 static class MyDate extends java.util.Date { 195 public MyDate(int time) { 196 super(time); 197 } 198 } 199 200 static class MyHashtable extends java.util.Hashtable { 201 private String name; 202 203 public MyHashtable(String name) { 204 this.name = name; 205 } 206 207 public synchronized boolean equals(Object o) { 208 return super.equals(o) && ((MyHashtable)o).name.equals(name); 209 } 210 } 211 212 public void testSupportsSubclassesOfClassesThatAlreadyHaveConverters() { 213 MyDate in = new MyDate(1234567890); 214 String xml = xstream.toXML(in); 215 assertObjectsEqual(in, xstream.fromXML(xml)); 216 217 MyHashtable in2 = new MyHashtable("hi"); 218 in2.put("cheese", "curry"); 219 in2.put("apple", new Integer (3)); 220 String xml2 = xstream.toXML(in2); 221 assertObjectsEqual(in2, xstream.fromXML(xml2)); 222 } 223 224 public static class ObjectWithNamedFields extends StandardObject implements Serializable { 225 226 private String name; 227 private int number; 228 private Software someSoftware; 229 private Object polymorphic; 230 private Object nothing; 231 232 private static final ObjectStreamField [] serialPersistentFields = { 233 new ObjectStreamField ("theName", String .class), 234 new ObjectStreamField ("theNumber", int.class), 235 new ObjectStreamField ("theSoftware", Software.class), 236 new ObjectStreamField ("thePolymorphic", Object .class), 237 new ObjectStreamField ("theNothing", Object .class) 238 }; 239 240 private void writeObject(ObjectOutputStream out) throws IOException { 241 ObjectOutputStream.PutField fields = out.putFields(); 243 fields.put("theName", name); 244 fields.put("theNumber", number); 245 fields.put("theSoftware", someSoftware); 246 fields.put("thePolymorphic", polymorphic); 247 fields.put("theNothing", nothing); 248 out.writeFields(); 249 } 250 251 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 252 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" 288 + " </default>\n" 289 + " </with-named-fields>\n" 290 + "</with-named-fields>"; 291 292 assertBothWays(obj, expectedXml); 293 } 294 295 public void testUsesDefaultIfNamedFieldNotFound() { 296 xstream.alias("with-named-fields", ObjectWithNamedFields.class); 297 xstream.alias("software", Software.class); 298 299 String inputXml = "" 300 + "<with-named-fields serialization=\"custom\">\n" 301 + " <with-named-fields>\n" 302 + " <default>\n" 303 + " <theSoftware>\n" 304 + " <vendor>tw</vendor>\n" 305 + " <name>xs</name>\n" 306 + " </theSoftware>\n" 307 + " <thePolymorphic class=\"com.thoughtworks.acceptance.objects.Hardware\">\n" 308 + " <arch>small</arch>\n" 309 + " <name>ipod</name>\n" 310 + " </thePolymorphic>\n" 311 + " </default>\n" 312 + " </with-named-fields>\n" 313 + "</with-named-fields>"; 314 315 ObjectWithNamedFields result = (ObjectWithNamedFields) xstream.fromXML(inputXml); 316 assertEquals(-1, result.number); 317 assertEquals("unknown", result.name); 318 assertEquals(new Software("tw", "xs"), result.someSoftware); 319 } 320 321 public void testCustomStreamWithNestedCustomStream() { 322 ObjectWithNamedFields outer = new ObjectWithNamedFields(); 323 outer.name = "Joe"; 324 outer.someSoftware = new Software("tw", "xs"); 325 outer.nothing = null; 326 327 ObjectWithNamedFields inner = new ObjectWithNamedFields(); 328 inner.name = "Thing"; 329 330 outer.polymorphic = inner; 331 332 xstream.alias("with-named-fields", ObjectWithNamedFields.class); 333 xstream.alias("software", Software.class); 334 335 String expectedXml = "" 336 + "<with-named-fields serialization=\"custom\">\n" 337 + " <with-named-fields>\n" 338 + " <default>\n" 339 + " <theName>Joe</theName>\n" 340 + " <theNumber>0</theNumber>\n" 341 + " <theSoftware>\n" 342 + " <vendor>tw</vendor>\n" 343 + " <name>xs</name>\n" 344 + " </theSoftware>\n" 345 + " <thePolymorphic class=\"with-named-fields\" serialization=\"custom\">\n" 346 + " <with-named-fields>\n" 347 + " <default>\n" 348 + " <theName>Thing</theName>\n" 349 + " <theNumber>0</theNumber>\n" 350 + " </default>\n" 351 + " </with-named-fields>\n" 352 + " </thePolymorphic>\n" 353 + " </default>\n" 354 + " </with-named-fields>\n" 355 + "</with-named-fields>"; 356 357 assertBothWays(outer, expectedXml); 358 } 359 360 public static class NoDefaultFields extends StandardObject implements Serializable { 361 362 private transient int something; 363 364 public NoDefaultFields() { 365 } 366 367 public NoDefaultFields(int something) { 368 this.something = something; 369 } 370 371 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 372 in.defaultReadObject(); 373 something = in.readInt(); 374 } 375 376 private void writeObject(ObjectOutputStream out) throws IOException { 377 out.defaultWriteObject(); 378 out.writeInt(something); 379 } 380 381 } 382 383 public void testObjectWithCallToDefaultWriteButNoDefaultFields() { 384 xstream.alias("x", NoDefaultFields.class); 385 386 String expectedXml = "" 387 + "<x serialization=\"custom\">\n" 388 + " <x>\n" 389 + " <default/>\n" 390 + " <int>77</int>\n" 391 + " </x>\n" 392 + "</x>"; 393 assertBothWays(new NoDefaultFields(77), expectedXml); 394 } 395 396 public void testMaintainsBackwardsCompatabilityWithXStream1_1_0FieldFormat() { 397 ObjectWithNamedFields outer = new ObjectWithNamedFields(); 398 outer.name = "Joe"; 399 outer.someSoftware = new Software("tw", "xs"); 400 outer.nothing = null; 401 402 ObjectWithNamedFields inner = new ObjectWithNamedFields(); 403 inner.name = "Thing"; 404 405 outer.polymorphic = inner; 406 407 xstream.alias("with-named-fields", ObjectWithNamedFields.class); 408 xstream.alias("software", Software.class); 409 410 String oldFormatOfXml = "" 411 + "<with-named-fields serialization=\"custom\">\n" 412 + " <with-named-fields>\n" 413 + " <fields>\n" 414 + " <field name=\"theName\" class=\"string\">Joe</field>\n" 415 + " <field name=\"theNumber\" class=\"int\">0</field>\n" 416 + " <field name=\"theSoftware\" class=\"software\">\n" 417 + " <vendor>tw</vendor>\n" 418 + " <name>xs</name>\n" 419 + " </field>\n" 420 + " <field name=\"thePolymorphic\" class=\"with-named-fields\" serialization=\"custom\">\n" 421 + " <with-named-fields>\n" 422 + " <fields>\n" 423 + " <field name=\"theName\" class=\"string\">Thing</field>\n" 424 + " <field name=\"theNumber\" class=\"int\">0</field>\n" 425 + " </fields>\n" 426 + " </with-named-fields>\n" 427 + " </field>\n" 428 + " </fields>\n" 429 + " </with-named-fields>\n" 430 + "</with-named-fields>"; 431 432 433 assertEquals(outer, xstream.fromXML(oldFormatOfXml)); 434 } 435 436 public static class ObjectWithNamedThatMatchRealFields extends StandardObject implements Serializable { 437 438 private String name; 439 private int number; 440 441 private void writeObject(ObjectOutputStream out) throws IOException { 442 ObjectOutputStream.PutField fields = out.putFields(); 443 fields.put("name", name.toUpperCase()); 444 fields.put("number", number * 100); 445 out.writeFields(); 446 } 447 448 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 449 ObjectInputStream.GetField fields = in.readFields(); 450 name = ((String ) fields.get("name", "unknown")).toLowerCase(); 451 number = fields.get("number", 10000) / 100; 452 } 453 454 } 455 456 public void testSupportsWritingFieldsForObjectsThatDoNotExplicitlyDefineThem() { 457 xstream.alias("an-object", ObjectWithNamedThatMatchRealFields.class); 458 459 ObjectWithNamedThatMatchRealFields input = new ObjectWithNamedThatMatchRealFields(); 460 input.name = "a name"; 461 input.number = 5; 462 463 String expectedXml = "" 464 + "<an-object serialization=\"custom\">\n" 465 + " <an-object>\n" 466 + " <default>\n" 467 + " <name>A NAME</name>\n" 468 + " <number>500</number>\n" 469 + " </default>\n" 470 + " </an-object>\n" 471 + "</an-object>"; 472 473 assertBothWays(input, expectedXml); 474 } 475 476 public static class ObjectThatReadsCustomFieldsButDoesNotWriteThem extends StandardObject implements Serializable { 477 478 private String name; 479 private int number; 480 481 private void writeObject(ObjectOutputStream out) throws IOException { 482 out.defaultWriteObject(); 483 } 484 485 private void readObject(ObjectInputStream in) throws IOException , ClassNotFoundException { 486 ObjectInputStream.GetField fields = in.readFields(); 487 name = ((String ) fields.get("name", "unknown")); 488 number = fields.get("number", 10000); 489 } 490 491 } 492 493 public void testSupportsGetFieldsWithoutPutFields() { 494 xstream.alias("an-object", ObjectThatReadsCustomFieldsButDoesNotWriteThem.class); 495 496 ObjectThatReadsCustomFieldsButDoesNotWriteThem input = new ObjectThatReadsCustomFieldsButDoesNotWriteThem(); 497 input.name = "a name"; 498 input.number = 5; 499 500 String expectedXml = "" 501 + "<an-object serialization=\"custom\">\n" 502 + " <an-object>\n" 503 + " <default>\n" 504 + " <number>5</number>\n" 505 + " <name>a name</name>\n" 506 + " </default>\n" 507 + " </an-object>\n" 508 + "</an-object>"; 509 510 assertBothWays(input, expectedXml); 511 } 512 } 513 | Popular Tags |