/* * Copyright (C) 2005, 2006 Joe Walnes. * Copyright (C) 2006, 2007, 2008 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 09. December 2005 by Joe Walnes */ package com.thoughtworks.acceptance; import com.thoughtworks.acceptance.objects.Software; import com.thoughtworks.acceptance.objects.StandardObject; import com.thoughtworks.xstream.MarshallingStrategy; import com.thoughtworks.xstream.alias.ClassMapper; import com.thoughtworks.xstream.converters.ConverterLookup; import com.thoughtworks.xstream.converters.DataHolder; import com.thoughtworks.xstream.core.DefaultConverterLookup; import com.thoughtworks.xstream.core.ReferenceByIdMarshaller; import com.thoughtworks.xstream.core.ReferenceByIdUnmarshaller; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppReader; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.testutil.CallLog; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; public class MultipleObjectsInOneStreamTest extends AbstractAcceptanceTest { public static class Person extends StandardObject { private String firstName; private String lastName; private Person secretary; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } public void testReadAndWriteMultipleObjectsInOneStream() { xstream.alias("person", Person.class); StringWriter buffer = new StringWriter(); // serialize HierarchicalStreamWriter writer = new PrettyPrintWriter(buffer); writer.startNode("people"); xstream.marshal(new Person("Postman", "Pat"), writer); xstream.marshal(new Person("Bob", "Builder"), writer); xstream.marshal(new Person("Tinky", "Winky"), writer); writer.endNode(); assertEquals("" + "<people>\n" + " <person>\n" + " <firstName>Postman</firstName>\n" + " <lastName>Pat</lastName>\n" + " </person>\n" + " <person>\n" + " <firstName>Bob</firstName>\n" + " <lastName>Builder</lastName>\n" + " </person>\n" + " <person>\n" + " <firstName>Tinky</firstName>\n" + " <lastName>Winky</lastName>\n" + " </person>\n" + "</people>", buffer.toString()); // deserialize HierarchicalStreamReader reader = new XppReader(new StringReader(buffer.toString())); assertTrue("should be another object to read (1)", reader.hasMoreChildren()); reader.moveDown(); assertEquals(new Person("Postman", "Pat"), xstream.unmarshal(reader)); reader.moveUp(); assertTrue("should be another object to read (2)", reader.hasMoreChildren()); reader.moveDown(); assertEquals(new Person("Bob", "Builder"), xstream.unmarshal(reader)); reader.moveUp(); assertTrue("should be another object to read (3)", reader.hasMoreChildren()); reader.moveDown(); assertEquals(new Person("Tinky", "Winky"), xstream.unmarshal(reader)); reader.moveUp(); assertFalse("should be no more objects", reader.hasMoreChildren()); } public void testDrivenThroughObjectStream() throws IOException, ClassNotFoundException { Writer writer = new StringWriter(); xstream.alias("software", Software.class); ObjectOutputStream oos = xstream.createObjectOutputStream(writer); oos.writeInt(123); oos.writeObject("hello"); oos.writeObject(new Software("tw", "xs")); oos.close(); String expectedXml = "" + "<object-stream>\n" + " <int>123</int>\n" + " <string>hello</string>\n" + " <software>\n" + " <vendor>tw</vendor>\n" + " <name>xs</name>\n" + " </software>\n" + "</object-stream>"; assertEquals(expectedXml, writer.toString()); ObjectInputStream ois = xstream.createObjectInputStream(new StringReader(writer .toString())); assertEquals(123, ois.readInt()); assertEquals("hello", ois.readObject()); assertEquals(new Software("tw", "xs"), ois.readObject()); try { ois.readObject(); // As far as I can see this is the only clue the // ObjectInputStream gives that it's done. fail("Expected EOFException"); } catch (EOFException expectedException) { // good } } public void testDrivenThroughCompressedObjectStream() throws IOException, ClassNotFoundException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Writer writer = new OutputStreamWriter(new DeflaterOutputStream(baos, new Deflater( Deflater.BEST_COMPRESSION)), "UTF-8"); xstream.alias("software", Software.class); ObjectOutputStream oos = xstream.createObjectOutputStream(writer); oos.writeInt(123); oos.writeObject("hello"); oos.writeObject(new Software("tw", "xs")); oos.flush(); oos.close(); byte[] data = baos.toByteArray(); assertTrue("Too less data: " + data.length, data.length > 2); ObjectInputStream ois = xstream.createObjectInputStream(new InputStreamReader( new InflaterInputStream(new ByteArrayInputStream(data), new Inflater()), "UTF-8")); assertEquals(123, ois.readInt()); assertEquals("hello", ois.readObject()); assertEquals(new Software("tw", "xs"), ois.readObject()); try { ois.readObject(); // As far as I can see this is the only clue the // ObjectInputStream gives that it's done. fail("Expected EOFException"); } catch (EOFException expectedException) { // good } } public void testObjectOutputStreamPropagatesCloseAndFlushEvents() throws IOException { // setup final CallLog log = new CallLog(); Writer loggingWriter = new Writer() { public void close() { log.actual("close"); } public void flush() { log.actual("flush"); } public void write(char cbuf[], int off, int len) { // don't care about this } }; // expectations log.expect("flush"); // TWO flushes are currently caused. Only one is needed, but // this is no big deal. log.expect("flush"); log.expect("close"); // execute ObjectOutputStream objectOutputStream = xstream.createObjectOutputStream(loggingWriter); objectOutputStream.flush(); objectOutputStream.close(); // verify log.verify(); } public void testObjectInputStreamPropegatesCloseEvent() throws IOException { // setup final CallLog log = new CallLog(); Reader loggingReader = new StringReader("<int>1</int>") { public void close() { log.actual("close"); } }; // expectations log.expect("close"); // execute ObjectInputStream objectInputStream = xstream.createObjectInputStream(loggingReader); objectInputStream.close(); // verify log.verify(); } public void testByDefaultDoesNotPreserveReferencesAcrossDifferentObjectsInStream() throws Exception { xstream.alias("person", Person.class); // Setup initial data: two object, one referencing another... Person alice = new Person("Alice", "Thing"); Person jane = new Person("Jane", "Blah"); jane.secretary = alice; // Serialize the two individual objects. StringWriter writer = new StringWriter(); ObjectOutputStream out = xstream.createObjectOutputStream(writer); out.writeObject(alice); out.writeObject(jane); out.close(); // Deserialize the two objects. ObjectInputStream in = xstream.createObjectInputStream(new StringReader(writer .toString())); alice = (Person)in.readObject(); jane = (Person)in.readObject(); in.close(); assertNotSame(alice, jane.secretary); // NOT SAME } static class ReusingReferenceByIdMarshallingStrategy implements MarshallingStrategy { private ReferenceByIdMarshaller marshaller; private ReferenceByIdUnmarshaller unmarshaller; public void marshal(HierarchicalStreamWriter writer, Object obj, ConverterLookup converterLookup, Mapper mapper, DataHolder dataHolder) { if (marshaller == null) { marshaller = new ReferenceByIdMarshaller(writer, converterLookup, mapper); } marshaller.start(obj, dataHolder); } public void marshal(HierarchicalStreamWriter writer, Object obj, DefaultConverterLookup converterLookup, ClassMapper classMapper, DataHolder dataHolder) { throw new UnsupportedOperationException(); } public Object unmarshal(Object root, HierarchicalStreamReader reader, DataHolder dataHolder, ConverterLookup converterLookup, Mapper mapper) { if (unmarshaller == null) { unmarshaller = new ReferenceByIdUnmarshaller( root, reader, converterLookup, mapper); } return unmarshaller.start(dataHolder); } public Object unmarshal(Object root, HierarchicalStreamReader reader, DataHolder dataHolder, DefaultConverterLookup converterLookup, ClassMapper classMapper) { throw new UnsupportedOperationException(); } } public void testSupportsOptionToPreserveReferencesAcrossDifferentObjectsInStream() throws Exception { xstream.alias("person", Person.class); xstream.setMarshallingStrategy(new ReusingReferenceByIdMarshallingStrategy()); // Setup initial data: two object, one referencing another... Person alice = new Person("Alice", "Thing"); Person jane = new Person("Jane", "Blah"); jane.secretary = alice; // Serialize the two individual objects. StringWriter writer = new StringWriter(); ObjectOutputStream out = xstream.createObjectOutputStream(writer); out.writeObject(alice); out.writeObject(jane); out.close(); // Deserialize the two objects. ObjectInputStream in = xstream.createObjectInputStream(new StringReader(writer .toString())); alice = (Person)in.readObject(); jane = (Person)in.readObject(); in.close(); assertSame(alice, jane.secretary); } }