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

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;

import com.thoughtworks.acceptance.objects.Software;
import com.thoughtworks.acceptance.objects.StandardObject;
import com.thoughtworks.acceptance.someobjects.WithList;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
import com.thoughtworks.xstream.core.ClassLoaderReference;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.mapper.CannotResolveClassException;
import com.thoughtworks.xstream.mapper.DefaultMapper;
import com.thoughtworks.xstream.mapper.Mapper;
import com.thoughtworks.xstream.mapper.MapperWrapper;

public class CustomMapperTest extends AbstractAcceptanceTest {

    /**
     * A sample mapper strips the underscore prefix of field names in the XML
     */
    private static class FieldPrefixStrippingMapper extends MapperWrapper {
        public FieldPrefixStrippingMapper(Mapper wrapped) {
            super(wrapped);
        }

        public String serializedMember(Class type, String memberName) {
            if (memberName.startsWith("_")) {
                // _blah -> blah
                memberName = memberName.substring(1); // chop off leading char (the underscore)
            } else if (memberName.startsWith("my")) {
                // myBlah -> blah
                memberName = memberName.substring(2, 3).toLowerCase() + memberName.substring(3);
            }
            return super.serializedMember(type, memberName);
        }

        public String realMember(Class type, String serialized) {
            String fieldName = super.realMember(type, serialized);
            // Not very efficient or elegant, but enough to get the point across.
            // Luckily the CachingMapper will ensure this is only ever called once per field per class.
            try {
                type.getDeclaredField("_" + fieldName);
                return "_" + fieldName;
            } catch (NoSuchFieldException e) {
                try {
                    String myified = "my" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                    type.getDeclaredField(myified);
                    return myified;
                } catch (NoSuchFieldException e2) {
                    return fieldName;
                }
            }
        }
    }

    public static class ThingWithStupidNamingConventions extends StandardObject {
        String _firstName;
        String lastName;
        int myAge;

        public ThingWithStupidNamingConventions(String firstname, String lastname, int age) {
            _firstName = firstname;
            this.lastName = lastname;
            myAge = age;
        }
    }

    public void testUserDefinedMappingCanAlterFieldName() {
        xstream = new XStream() {
            protected MapperWrapper wrapMapper(MapperWrapper next) {
                return new FieldPrefixStrippingMapper(next);
            }
        };
        setupSecurity(xstream);
        xstream.alias("thing", ThingWithStupidNamingConventions.class);

        ThingWithStupidNamingConventions in = new ThingWithStupidNamingConventions("Joe", "Walnes", 10);
        String expectedXml = ""
                + "<thing>\n"
                + "  <firstName>Joe</firstName>\n" // look, no underscores!
                + "  <lastName>Walnes</lastName>\n"
                + "  <age>10</age>\n"
                + "</thing>";

        assertBothWays(in, expectedXml);
    }

    private static class PackageStrippingMapper extends MapperWrapper {
        public PackageStrippingMapper(Mapper wrapped) {
            super(wrapped);
        }

        public String serializedClass(Class type) {
            return type.getName().replaceFirst(".*\\.", "");
        }
    }
    
    public void testStripsPackagesUponDeserialization() {
        // obviously this isn't deserializable!
        xstream = new XStream() {
            protected MapperWrapper wrapMapper(MapperWrapper next) {
                return new PackageStrippingMapper(next);
            }
        };

        // NOTE: no aliases defined!

        String expectedXml = "" +
                "<Software>\n" +
                "  <vendor>ms</vendor>\n" +
                "  <name>word</name>\n" +
                "</Software>";
        assertEquals(expectedXml, xstream.toXML(new Software("ms", "word")));
    }
    
    public void testOwnMapperChainCanBeRegistered() {
        ClassLoaderReference classLoaderReference = new ClassLoaderReference(getClass().getClassLoader());
        Mapper mapper = new DefaultMapper(classLoaderReference);
        xstream = new XStream(new PureJavaReflectionProvider(), new DomDriver(), getClass().getClassLoader(), mapper);
        
        String expected = "" +
                "<com.thoughtworks.acceptance.objects.Software>\n" +
                "  <vendor>ms</vendor>\n" +
                "  <name>word</name>\n" +
                "</com.thoughtworks.acceptance.objects.Software>";
        assertEquals(expected, xstream.toXML(new Software("ms", "word")));
    }
    
    public void testCanBeUsedToOmitUnexpectedElements() {
        String expectedXml = "" +
                "<software>\n" +
                "  <version>1.0</version>\n" +
                "  <vendor>Joe</vendor>\n" +
                "  <name>XStream</name>\n" +
                "</software>";

        xstream = new XStream() {

            protected MapperWrapper wrapMapper(MapperWrapper next) {
                return new MapperWrapper(next) {

                    public boolean shouldSerializeMember(Class definedIn, String fieldName) {
                        return definedIn != Object.class ? super.shouldSerializeMember(definedIn, fieldName) : false;
                    }
                    
                };
            }
            
        };
        setupSecurity(xstream);
        xstream.alias("software", Software.class);

        Software out = (Software) xstream.fromXML(expectedXml);
        assertEquals("Joe", out.vendor);
        assertEquals("XStream", out.name);
    }

    public void testInjectingReplacements() {
        XStream xstream = new XStream() {

            protected MapperWrapper wrapMapper(MapperWrapper next) {
                return new MapperWrapper(next) {
                    public Class realClass(String elementName) {
                        try {
                            return super.realClass(elementName);

                        } catch (CannotResolveClassException e) {
                            if (elementName.endsWith("oo")) {
                                return Integer.class;
                            }
                            if (elementName.equals("UnknownList")) {
                                return LinkedList.class;
                            }
                            throw e;
                        }
                    }
                    
                };
            }

        };
        setupSecurity(xstream);
        xstream.alias("wl", WithList.class);
        WithList wl = (WithList)xstream.fromXML("" 
                + "<wl>\n" 
                + "  <things class='UnknownList'>\n" 
                + "    <foo>1</foo>\n" 
                + "    <cocoo>2</cocoo>\n" 
                + "  </things>\n" 
                + "</wl>");
        assertEquals(new ArrayList(Arrays.asList(new Integer[]{new Integer(1), new Integer(2)})), wl.things);
        assertTrue(wl.things instanceof LinkedList);
    }
}
