/*
 * Copyright (C) 2004, 2005 Joe Walnes.
 * Copyright (C) 2006, 2007, 2008, 2009, 2014, 2015 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 24. August 2004 by Joe Walnes
 */
package com.thoughtworks.acceptance;

import com.thoughtworks.acceptance.objects.Original;
import com.thoughtworks.acceptance.objects.Replaced;
import com.thoughtworks.acceptance.objects.StandardObject;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.StringUtils;

public class WriteReplaceTest extends AbstractAcceptanceTest {

    public static class Thing extends StandardObject implements Serializable {

        int a;
        int b;

        public Thing() {
        }

        public Thing(int a, int b) {
            this.a = a;
            this.b = b;
        }

        private Object writeReplace() {
            return new Thing(a * 1000, b * 1000);
        }

        private Object readResolve() {
            return new Thing(a / 1000, b / 1000);
        }

    }

    public void testReplacesAndResolves() throws IOException, ClassNotFoundException {
        Thing thing = new Thing(3, 6);

        // ensure that Java serialization does not cause endless loop for a Thing
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(thing);
        oos.close();
        
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ios = new ObjectInputStream(bais);
        assertEquals(thing, ios.readObject());
        ios.close();
        
        // ensure that XStream does not cause endless loop for a Thing
        xstream.alias("thing", Thing.class);

        String expectedXml = ""
                + "<thing>\n"
                + "  <a>3000</a>\n"
                + "  <b>6000</b>\n"
                + "</thing>";

        assertBothWays(thing, expectedXml);
    }

    public void testAllowsDifferentTypeToBeSubstituted() {
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        Original in = new Original("hello world");

        String expectedXml = ""
                + "<original-class resolves-to=\"replaced-class\">\n"
                + "  <replacedValue>HELLO WORLD</replacedValue>\n"
                + "</original-class>";

        assertBothWays(in, expectedXml);
    }

    public void testAllowsDifferentTypeToBeSubstitutedInList() {
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        List in = new ArrayList(); 
        in.add(new Original("hello world"));

        String expectedXml = ""
                + "<list>\n"
                + "  <original-class resolves-to=\"replaced-class\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original-class>\n" 
                + "</list>";

        assertBothWays(in, expectedXml);
    }

    public static class Container extends StandardObject {
        Original original;
    }
    
    public void testAllowsDifferentTypeToBeSubstitutedAsMember() {
        xstream.alias("container", Container.class);
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        Container in = new Container(); 
        in.original = new Original("hello world");

        String expectedXml = ""
                + "<container>\n"
                + "  <original resolves-to=\"replaced-class\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original>\n" 
                + "</container>";

        assertBothWays(in, expectedXml);
    }

    public static class ExternalizableContainer extends StandardObject implements Externalizable {
        Original original;
        
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            original = (Original)in.readObject();
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(original);
        }
    }
    
    public void testAllowsDifferentTypeToBeSubstitutedInExternalizable() {
        xstream.alias("container", ExternalizableContainer.class);
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        ExternalizableContainer in = new ExternalizableContainer(); 
        in.original = new Original("hello world");

        String expectedXml = ""
                + "<container>\n"
                + "  <original-class resolves-to=\"replaced-class\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original-class>\n" 
                + "</container>";

        assertBothWays(in, expectedXml);
    }

    public void testAllowsDifferentTypeToBeSubstitutedWithNonExistingClass() {
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        Original in = new Original("hello world");

        String xml = ""
                + "<original-class resolves-to=\"replaced-class\" class=\"not.Existing\">\n"
                + "  <replacedValue>HELLO WORLD</replacedValue>\n"
                + "</original-class>";

        assertEquals(in, xstream.fromXML(xml));
    }

    public void testAllowsDifferentTypeToBeSubstitutedWithNonExistingClassInList() {
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        List in = new ArrayList(); 
        in.add(new Original("hello world"));

        String xml = ""
                + "<list>\n"
                + "  <original-class resolves-to=\"replaced-class\" class=\"not.Existing\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original-class>\n" 
                + "</list>";

        assertEquals(in, xstream.fromXML(xml));
    }

    public void testAllowsDifferentTypeToBeSubstitutedWithNonExistingClassAsMember() {
        xstream.alias("container", Container.class);
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        Container in = new Container(); 
        in.original = new Original("hello world");

        String xml = ""
                + "<container>\n"
                + "  <original resolves-to=\"replaced-class\" class=\"not.Existing\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original>\n" 
                + "</container>";

        assertEquals(in, xstream.fromXML(xml));
    }
    
    public void testAllowsDifferentTypeToBeSubstitutedWithNonExistingClassInExternalizable() {
        xstream.alias("container", ExternalizableContainer.class);
        xstream.alias("original-class", Original.class);
        xstream.alias("replaced-class", Replaced.class);

        ExternalizableContainer in = new ExternalizableContainer(); 
        in.original = new Original("hello world");

        String xml = ""
                + "<container>\n"
                + "  <original-class resolves-to=\"replaced-class\" class=\"not.Existing\">\n"
                + "    <replacedValue>HELLO WORLD</replacedValue>\n"
                + "  </original-class>\n" 
                + "</container>";

        assertEquals(in, xstream.fromXML(xml));
    }

    public static class OriginalSerializable extends StandardObject {
        String originalValue;

        public OriginalSerializable() {
        }

        public OriginalSerializable(String originalValue) {
            this.originalValue = originalValue;
        }

        private Object writeReplace() {
            return new ReplacedSerializable(originalValue.toUpperCase());
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
        }
    }

    public static class ReplacedSerializable extends StandardObject {
        String replacedValue;

        public ReplacedSerializable() {
        }

        public ReplacedSerializable(String replacedValue) {
            this.replacedValue = replacedValue;
        }

        private Object readResolve() {
            return new OriginalSerializable(replacedValue.toLowerCase());
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            in.defaultReadObject();
        }
    }

    public void testAllowsDifferentTypeToBeSubstitutedForCustomSerializableObjects() {
        xstream.alias("original-serializable-class", OriginalSerializable.class);
        xstream.alias("replaced-serializable-class", ReplacedSerializable.class);

        OriginalSerializable in = new OriginalSerializable("hello world");

        String expectedXml = ""
                + "<original-serializable-class resolves-to=\"replaced-serializable-class\" serialization=\"custom\">\n"
                + "  <replaced-serializable-class>\n"
                + "    <default>\n"
                + "      <replacedValue>HELLO WORLD</replacedValue>\n"
                + "    </default>\n"
                + "  </replaced-serializable-class>\n"
                + "</original-serializable-class>";

        assertBothWays(in, expectedXml);
    }

    public static class OriginalExternalizable extends StandardObject implements Externalizable {
        String originalValue;

        public OriginalExternalizable() {
        }

        public OriginalExternalizable(String originalValue) {
            this.originalValue = originalValue;
        }

        private Object writeReplace() {
            return new ReplacedExternalizable(originalValue.toUpperCase());
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(originalValue);
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            originalValue = (String)in.readObject();
        }
    }

    public static class ReplacedExternalizable extends StandardObject implements Externalizable {
        String replacedValue;

        public ReplacedExternalizable() {
        }

        public ReplacedExternalizable(String replacedValue) {
            this.replacedValue = replacedValue;
        }

        private Object readResolve() {
            return new OriginalExternalizable(replacedValue.toLowerCase());
        }

        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(StringUtils.reverse(replacedValue));
        }

        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            replacedValue = StringUtils.reverse((String)in.readObject());
        }
    }

    public void testAllowsDifferentTypeToBeSubstitutedForCustomExternalizableObjects() {
        xstream.alias("original-externalizable-class", OriginalExternalizable.class);
        xstream.alias("replaced-externalizable-class", ReplacedExternalizable.class);

        OriginalExternalizable in = new OriginalExternalizable("hello world");

        String expectedXml = ""
                + "<original-externalizable-class resolves-to=\"replaced-externalizable-class\">\n"
                + "  <string>DLROW OLLEH</string>\n"
                + "</original-externalizable-class>";

        assertBothWays(in, expectedXml);
    }
}
