/*
 * Copyright (C) 2003, 2004 Joe Walnes.
 * Copyright (C) 2006, 2007, 2011 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 26. September 2003 by Joe Walnes
 */
package com.thoughtworks.acceptance;

import com.thoughtworks.acceptance.objects.StandardObject;
import com.thoughtworks.xstream.converters.reflection.ReflectionConverter;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.io.xml.XppReader;

import java.io.StringReader;

public class CustomClassesTest extends AbstractAcceptanceTest {

    public static class SamplePerson extends StandardObject {
        int anInt;
        String firstName;
        String lastName;
        transient String aComment = "";
    }

    public void testCustomObjectWithBasicFields() {

        xstream.alias("friend", SamplePerson.class);

        SamplePerson person = new SamplePerson();
        person.anInt = 3;
        person.firstName = "Joe";
        person.lastName = "Walnes";

        String expected =
                "<friend>\n" +
                "  <anInt>3</anInt>\n" +
                "  <firstName>Joe</firstName>\n" +
                "  <lastName>Walnes</lastName>\n" +
                "</friend>";

        assertBothWays(person, expected);

    }

    public static class SamplePersonHolder {
        String aString;
        SamplePerson brother;

        public boolean equals(Object obj) {
            SamplePersonHolder containerObject = (SamplePersonHolder) obj;
            return (aString == null ? containerObject.aString == null : aString.equals(containerObject.aString))
                    && brother.equals(containerObject.brother);
        }
    }

    public void testCustomObjectWithCustomObjectField() {
        xstream.alias("friend", SamplePerson.class);
        xstream.alias("personHolder", SamplePersonHolder.class);

        SamplePersonHolder personHolder = new SamplePersonHolder();
        personHolder.aString = "hello world";

        SamplePerson person = new SamplePerson();
        person.anInt = 3;
        person.firstName = "Joe";
        person.lastName = "Walnes";

        personHolder.brother = person;

        String expected =
                "<personHolder>\n" +
                "  <aString>hello world</aString>\n" +
                "  <brother>\n" +
                "    <anInt>3</anInt>\n" +
                "    <firstName>Joe</firstName>\n" +
                "    <lastName>Walnes</lastName>\n" +
                "  </brother>\n" +
                "</personHolder>";

        assertBothWays(personHolder, expected);

    }

    public void testCustomObjectWithCustomObjectFieldsSetToNull() {
        xstream.alias("friend", SamplePerson.class);
        xstream.alias("personHolder", SamplePersonHolder.class);

        SamplePersonHolder personHolder = new SamplePersonHolder();
        personHolder.aString = null;

        SamplePerson person = new SamplePerson();
        person.anInt = 3;
        person.firstName = "Joe";
        person.lastName = null;

        personHolder.brother = person;

        String expected =
                "<personHolder>\n" +
                "  <brother>\n" +
                "    <anInt>3</anInt>\n" +
                "    <firstName>Joe</firstName>\n" +
                "  </brother>\n" +
                "</personHolder>";

        assertBothWays(personHolder, expected);

    }

    public void testCustomObjectCanBeInstantiatedExternallyBeforeDeserialization() {
        xstream.alias("friend", SamplePerson.class);
        xstream.alias("personHolder", SamplePersonHolder.class);

        String xml =
                "<personHolder>\n" +
                "  <aString>hello world</aString>\n" +
                "  <brother>\n" +
                "    <anInt>3</anInt>\n" +
                "    <firstName>Joe</firstName>\n" +
                "    <lastName>Walnes</lastName>\n" +
                "  </brother>\n" +
                "</personHolder>";

        // execute
        SamplePersonHolder alreadyInstantiated = new SamplePersonHolder();
        xstream.unmarshal(new XppReader(new StringReader(xml)), alreadyInstantiated);

        // verify
        SamplePersonHolder expectedResult = new SamplePersonHolder();
        expectedResult.aString = "hello world";

        SamplePerson expectedPerson = new SamplePerson();
        expectedPerson.anInt = 3;
        expectedPerson.firstName = "Joe";
        expectedPerson.lastName = "Walnes";
        expectedResult.brother = expectedPerson;

        assertEquals(expectedResult, alreadyInstantiated);
    }

    public void testCustomObjectWillNotUnmarshalTransientFields() {

        xstream.alias("friend", SamplePerson.class);

        String xml =
                "<friend>\n" +
                "  <anInt>3</anInt>\n" +
                "  <firstName>Joe</firstName>\n" +
                "  <lastName>Walnes</lastName>\n" +
                "  <aComment>XStream Despot</aComment>\n" +
                "</friend>";

        SamplePerson person = (SamplePerson)xstream.fromXML(xml);
        if (JVM.is14()) {
            assertNull(person.aComment);
        } else {
            assertEquals("", person.aComment);
        }
    }
    
    static class Joe extends SamplePerson {
        boolean aBoolean;
    }

    public void testCustomObjectWillNotUnmarshalInheritedTransientFields() {

        xstream.alias("joe", Joe.class);

        String xml =
                "<joe>\n" +
                "  <anInt>3</anInt>\n" +
                "  <firstName>Joe</firstName>\n" +
                "  <lastName>Walnes</lastName>\n" +
                "  <aComment>XStream Despot</aComment>\n" +
                "  <aBoolean>true</aBoolean>\n" +
                "</joe>";

        Joe joe = (Joe)xstream.fromXML(xml);
        if (JVM.is14()) {
            assertNull(joe.aComment);
        } else {
            assertEquals("", joe.aComment);
        }
    }

    public void testCustomObjectWillNotUnmarshalTransientFieldsFromAttributes() {

        xstream.alias("friend", SamplePerson.class);

        String xml =
                "<friend aComment='XStream Despot'>\n" +
                "  <anInt>3</anInt>\n" +
                "  <firstName>Joe</firstName>\n" +
                "  <lastName>Walnes</lastName>\n" +
                "</friend>";

        // without attribute definition
        SamplePerson person = (SamplePerson)xstream.fromXML(xml);
        if (JVM.is14()) {
            assertNull(person.aComment);
        } else {
            assertEquals("", person.aComment);
        }

        xstream.useAttributeFor("aComment", String.class);

        // with attribute definition
        person = (SamplePerson)xstream.fromXML(xml);
        if (JVM.is14()) {
            assertNull(person.aComment);
        } else {
            assertEquals("", person.aComment);
        }
    }

    public void testNullObjectsDoNotHaveFieldsWritten() {

        xstream.alias("cls", WithSomeFields.class);

        WithSomeFields obj = new WithSomeFields();

        String expected = "<cls/>";

        assertBothWays(obj, expected);
    }

    public void testEmptyStringsAreNotTreatedAsNulls() {
        xstream.alias("cls", WithSomeFields.class);

        WithSomeFields obj = new WithSomeFields();
        obj.b = "";

        String expected = "" +
                "<cls>\n" +
                "  <b></b>\n" +
                "</cls>";

        assertBothWays(obj, expected);
    }

    public static class WithSomeFields extends StandardObject {
        Object a;
        String b;
    }

    public void testNullsAreDistinguishedFromEmptyStrings() {
        LotsOfStrings in = new LotsOfStrings();
        in.a = ".";
        in.b = "";
        in.c = null;

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

        assertEquals(".", out.a);
        assertEquals("", out.b);
        assertNull(out.c);
    }

    public static class LotsOfStrings {
        String a;
        String b;
        String c;
    }

    public void testFieldWithObjectType() {
        String expected = "" +
                "<thing>\n" +
                "  <one>1.0</one>\n" +
                "  <two class=\"double\">2.0</two>\n" +
                "</thing>";
        xstream.alias("thing", FieldWithObjectType.class);

        assertBothWays(new FieldWithObjectType(), expected);
    }

    public static class FieldWithObjectType extends StandardObject {
        Double one = new Double(1.0);
        Object two = new Double(2.0);
    }

    public void testFailsFastIfFieldIsDefinedTwice() {
        String input = "" +
                "<thing>\n" +
                "  <one>1.0</one>\n" +
                "  <one>2.0</one>\n" +
                "</thing>";
        xstream.alias("thing", FieldWithObjectType.class);

        try {

            xstream.fromXML(input);
            fail("Expected exception");

        } catch (ReflectionConverter.DuplicateFieldException expected) {
            assertEquals("one", expected.get("field"));
        }
    }
    
    public static class TransientInitializingClass extends StandardObject {
        private transient String s = "";
        private Object readResolve() {
            this.s = "foo";
            return this;
        }
    }

    public void testCustomObjectWithTransientFieldInitialization() {

        xstream.alias("tran", TransientInitializingClass.class);

        TransientInitializingClass tran = new TransientInitializingClass();

        String expected = "<tran/>";

        TransientInitializingClass serialized = (TransientInitializingClass)assertBothWays(tran, expected);
        assertEquals("foo", serialized.s);
    }
}
