/*
 * 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 14. August 2004 by Joe Walnes
 */
package com.thoughtworks.xstream.io.xml;

import com.megginson.sax.DataWriter;
import com.thoughtworks.acceptance.someobjects.X;
import com.thoughtworks.acceptance.someobjects.Y;
import com.thoughtworks.xstream.XStream;

import junit.framework.TestCase;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/*
 * @author Laurent Bihanic
 */
public class SaxWriterTest extends TestCase {

    private final static String IDENTITY_STYLESHEET =
            "<xsl:stylesheet version=\"1.0\"" +
            "                xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n" +
            "\n" +
            "  <xsl:output method=\"xml\"" +
            "              omit-xml-declaration=\"yes\" indent=\"no\"/>\n" +
            "\n" +
            "  <xsl:template" +
            "      match=\"*|@*|comment()|processing-instruction()|text()\">\n" +
            "    <xsl:copy>\n" +
            "      <xsl:apply-templates" +
            "          select=\"*|@*|comment()|processing-instruction()|text()\"/>\n" +
            "    </xsl:copy>\n" +
            "  </xsl:template>\n" +
            "</xsl:stylesheet>";

    private XStream xstream;
    private X testInput;
    private Templates identityStylesheet;

    protected void setUp() throws Exception {
        super.setUp();
        xstream = new XStream();
        xstream.alias("x", X.class);
        xstream.alias("y", Y.class);

        testInput = new X();
        testInput.anInt = 9;
        testInput.aStr = "zzz";
        testInput.innerObj = new Y();
        testInput.innerObj.yField = "ooo";

        identityStylesheet = TransformerFactory.newInstance().newTemplates(new StreamSource(new StringReader(IDENTITY_STYLESHEET)));
    }

    public void testMarshalsObjectToSAX() {
        String expected =
                "<?xml version=\"1.0\" standalone=\"yes\"?>\n\n" +
                "<x>\n" +
                "  <aStr>zzz</aStr>\n" +
                "  <anInt>9</anInt>\n" +
                "  <innerObj>\n" +
                "    <yField>ooo</yField>\n" +
                "  </innerObj>\n" +
                "</x>\n\n";

        Writer buffer = new StringWriter();
        SaxWriter writer = new SaxWriter();
        DataWriter outputter = new DataWriter(writer, buffer);
        outputter.setIndentStep(2);

        writer.setContentHandler(outputter);

        xstream.marshal(testInput, writer);

        assertEquals(expected, buffer.toString());
    }

    public void testAllowsStartAndEndDocCallbacksToBeSkipped() {
        String expected =
                "<int>1</int>\n" +
                "<int>2</int>\n" +
                "<int>3</int>\n";

        Writer buffer = new StringWriter();
        SaxWriter writer = new SaxWriter(false);
        DataWriter outputter = new DataWriter(writer, buffer);
        outputter.setIndentStep(2);

        writer.setContentHandler(outputter);

        xstream.marshal(new Integer(1), writer);
        xstream.marshal(new Integer(2), writer);
        xstream.marshal(new Integer(3), writer);

        assertEquals(expected, buffer.toString());
    }

    public void testMarshalsObjectToTrAX() throws Exception {
        String expected =
                "<x><aStr>zzz</aStr><anInt>9</anInt>" +
                "<innerObj><yField>ooo</yField></innerObj>" +
                "</x>" +
                "<y><yField>ooo</yField></y>";

        TraxSource traxSource = new TraxSource();
        traxSource.setXStream(xstream);
        traxSource.setSourceAsList(Arrays.asList(new Object[]{testInput, testInput.innerObj}));

        Writer buffer = new StringWriter();
        Transformer transformer = identityStylesheet.newTransformer();

        transformer.transform(traxSource, new StreamResult(buffer));

        assertEquals(expected, buffer.toString());
    }

    public void testNullSourceObject() {
        TraxSource traxSource = new TraxSource();

        try {
            traxSource.setSource(null);
            fail("Null source object not rejected");
        } catch (IllegalArgumentException e) { /* good! */
        }
    }

    public void testNullSourceList() {
        TraxSource traxSource = new TraxSource();

        try {
            traxSource.setSourceAsList(null);
            fail("Null source list not rejected");
        } catch (IllegalArgumentException e) { /* good! */
        }
    }

    public void testEmptySourceList() {
        TraxSource traxSource = new TraxSource();

        try {
            traxSource.setSourceAsList(Collections.EMPTY_LIST);
            fail("Empty source list not rejected");
        } catch (IllegalArgumentException e) { /* good! */
        }
    }

    /**
     * This method tests a quite insidious side-effect of
     * XStreamSource delaying the allocation and configuration of
     * the SAXWriter until the XSLT processor requests it.
     * <p/>
     * SAXWriter performs a copy of the source list contents upon
     * property setting to avoid objects being added or removed from
     * the list during the parse.</p>
     * <p/>
     * To avoid just another list copy, XStreamSource does not
     * protect itself against list changes. Hence, it is possible
     * for an application to configure the XStreamSource and then
     * empty the list prior triggering the XSL transformation.</p>.
     * <p/>
     * This method ensures SAXWriter indeed checks the list content
     * prior starting the parse.</p>
     */
    public void testEmptySourceListAtParse() throws Exception {
        TraxSource traxSource = new TraxSource();
        Writer buffer = new StringWriter();

        List list = new ArrayList();
        list.add(testInput);

        traxSource.setSourceAsList(list);
        list.clear();

        Transformer transformer = identityStylesheet.newTransformer();
        transformer.setErrorListener(new TrAXErrorListener());

        try {
            transformer.transform(traxSource, new StreamResult(buffer));

            fail("Empty source list not rejected");
        } catch (Exception expectedException) {
            if (expectedException.getMessage().endsWith("shall not be an empty list")) {
                // Good!
            } else {
                throw expectedException;
            }
        } 
    }

    private static class TrAXErrorListener implements ErrorListener {
        public TrAXErrorListener() {
            super();
        }

        public void warning(TransformerException e) {
            /* Ignore... */
        }

        public void error(TransformerException e) {
            /* Ignore... */
        }

        public void fatalError(TransformerException e)
                throws TransformerException {
            throw e;
        }
    }
}

