/*
 * Copyright (C) 2004 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 07. May 2004 by Joe Walnes
 */
package com.thoughtworks.acceptance;

import com.thoughtworks.acceptance.objects.StandardObject;
import com.thoughtworks.acceptance.someobjects.WithNamedList;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.core.AbstractReferenceMarshaller;

import java.util.ArrayList;
import java.util.List;

public abstract class AbstractDuplicateReferenceTest extends AbstractAcceptanceTest {

    protected void setUp() throws Exception {
        super.setUp();
        xstream.alias("thing", Thing.class);
    }

    public void testReferencesAreWorking() {

        Thing sameThing = new Thing("hello");
        Thing anotherThing = new Thing("hello");

        List list = new ArrayList();
        list.add(sameThing);
        list.add(sameThing);
        list.add(anotherThing);

        String xml = xstream.toXML(list);
        List result = (List) xstream.fromXML(xml);

        assertEquals(list, result);
    }

    public void testReferencesAreTheSameObjectWhenDeserialized() {

        Thing sameThing = new Thing("hello");
        Thing anotherThing = new Thing("hello");

        List list = new ArrayList();
        list.add(sameThing);
        list.add(sameThing);
        list.add(anotherThing);

        String xml = xstream.toXML(list);
        List result = (List) xstream.fromXML(xml);

        Thing t0 = (Thing) result.get(0);
        Thing t1 = (Thing) result.get(1);
        Thing t2 = (Thing) result.get(2);

        t0.field = "bye";

        assertEquals("bye", t0.field);
        assertEquals("bye", t1.field);
        assertEquals("hello", t2.field);

    }

    public static class Thing extends StandardObject {
        public String field;

        public Thing() {
        }

        public Thing(String field) {
            this.field = field;
        }
    }

    public static class MultRef {
        public Object s1 = new Object();
        public Object s2 = s1;
    }

    public void testMultipleReferencesToObjectsWithNoChildren() {
        MultRef in = new MultRef();
        assertSame(in.s1, in.s2);

        String xml = xstream.toXML(in);
        MultRef out = (MultRef) xstream.fromXML(xml);

        assertSame(out.s1, out.s2);
    }

    public void testReferencesNotUsedForImmutableValueTypes() {
        MultRef in = new MultRef();
        in.s1 = new Integer(4);
        in.s2 = in.s1;

        String xml = xstream.toXML(in);
        MultRef out = (MultRef) xstream.fromXML(xml);

        assertEquals(out.s1, out.s2);
        assertNotSame(out.s1, out.s2);
    }

    public void testReferencesUsedForMutableValueTypes() {
        MultRef in = new MultRef();
        in.s1 = new StringBuffer("hi");
        in.s2 = in.s1;

        String xml = xstream.toXML(in);
        MultRef out = (MultRef) xstream.fromXML(xml);

        StringBuffer buffer = (StringBuffer) out.s2;
        buffer.append("bye");

        assertEquals("hibye", out.s1.toString());
        assertSame(out.s1, out.s2);
    }

    public void testReferencesToImplicitCollectionIsNotPossible() {
        xstream.alias("strings", WithNamedList.class);
        xstream.addImplicitCollection(WithNamedList.class, "things");
        WithNamedList[] wls = new WithNamedList[]{
                new WithNamedList("foo"), new WithNamedList("bar")};
        wls[0].things.add("Hello");
        wls[0].things.add("Daniel");
        wls[1].things = wls[0].things;
    
        try {
            xstream.toXML(wls);
            fail("Thrown " + AbstractReferenceMarshaller.ReferencedImplicitElementException.class.getName() + " expected");
        } catch (final AbstractReferenceMarshaller.ReferencedImplicitElementException e) {
            // OK
        }
    }

    public void testReferencesToElementsOfImplicitCollectionIsPossible() {
        xstream.alias("strings", WithNamedList.class);
        xstream.addImplicitCollection(WithNamedList.class, "things");
        WithNamedList[] wls = new WithNamedList[]{
                new WithNamedList("foo"), new WithNamedList("bar")};
        wls[0].things.add("Hello");
        wls[0].things.add("Daniel");
        wls[1].things.add(wls[0]);
        
        String xml = xstream.toXML(wls);
        WithNamedList[] out = (WithNamedList[]) xstream.fromXML(xml);

        assertSame(out[0], out[1].things.get(0));
    }

    public void testReferencesToElementsOfNthImplicitCollectionIsPossible() {
        xstream.alias("strings", WithNamedList.class);
        xstream.addImplicitCollection(WithNamedList.class, "things");
        WithNamedList[] wls = new WithNamedList[]{
                new WithNamedList("foo"), new WithNamedList("bar"), new WithNamedList("foobar")};
        wls[1].things.add("Hello");
        wls[1].things.add("Daniel");
        wls[2].things.add(wls[1]);
        
        String xml = xstream.toXML(wls);
        WithNamedList[] out = (WithNamedList[]) xstream.fromXML(xml);

        assertSame(out[1], out[2].things.get(0));
    }
    
    public void testThrowsForInvalidReference() {
        String xml = "" // 
            + "<list>\n"
            + "  <thing>\n"
            + "    <field>Hello</field>\n"
            + "  </thing>\n"
            + "  <thing reference=\"foo\">\n"
            + "</list>";

        try {
            xstream.fromXML(xml);
            fail("Thrown " + ConversionException.class.getName() + " expected");
        } catch (final ConversionException e) {
            assertEquals("foo", e.get("reference"));
        }
    }
}
