TestClosing.java

package stax2.wstream;

import java.io.*;

import javax.xml.stream.*;
import javax.xml.transform.stream.StreamResult;

import org.codehaus.stax2.*;
import org.codehaus.stax2.io.Stax2BlockResult;

/**
 * This unit test suite verifies that the auto-closing feature works
 * as expected (both explicitly, and via Result object being passed).
 */
@SuppressWarnings("resource")
public class TestClosing
    extends BaseWriterTest
{
    /**
     * This unit test checks the default behaviour; with no auto-close, no
     * automatic closing should occur, nor explicit one unless specific
     * forcing method is used.
     */
    public void testNoAutoCloseWriter()
        throws XMLStreamException
    {
        XMLOutputFactory2 f = getFactory(false);
        MyWriter output = new MyWriter();
        XMLStreamWriter2 sw = (XMLStreamWriter2) f.createXMLStreamWriter(output);
        // shouldn't be closed to begin with...
        assertFalse(output.isClosed());
        writeDoc(sw);
        assertFalse(output.isClosed());

        // nor closed half-way through with basic close()
        sw.close();
        assertFalse(output.isClosed());

        // but needs to close when forced to:
        sw.closeCompletely();
        assertTrue(output.isClosed());

        // ... and should be ok to call it multiple times:
        sw.closeCompletely();
        sw.closeCompletely();
        assertTrue(output.isClosed());
    }

    public void testNoAutoCloseStream()
        throws XMLStreamException
    {
        XMLOutputFactory2 f = getFactory(false);
        MyStream output = new MyStream();
        XMLStreamWriter2 sw = (XMLStreamWriter2) f.createXMLStreamWriter(output, "UTF-8");
        // shouldn't be closed to begin with...
        assertFalse(output.isClosed());
        writeDoc(sw);
        assertFalse(output.isClosed());

        // nor closed half-way through with basic close()
        sw.close();
        assertFalse(output.isClosed());

        // but needs to close when forced to:
        sw.closeCompletely();
        assertTrue(output.isClosed());

        // ... and should be ok to call it multiple times:
        sw.closeCompletely();
        sw.closeCompletely();
        assertTrue(output.isClosed());
    }

    /**
     * This unit test checks that when auto-closing option is set, the
     * passed in output stream does get properly closed
     * when we call close(), as well as when do writeEndDocument().
     */
    public void testEnabledAutoClose()
        throws XMLStreamException
    {
        // First, explicit close:
        XMLOutputFactory2 f = getFactory(true);
        MyWriter output = new MyWriter();
        XMLStreamWriter2 sw = (XMLStreamWriter2) f.createXMLStreamWriter(output);
        assertFalse(output.isClosed());

        writeDoc(sw);

        sw.close();
        assertTrue(output.isClosed());

        // also, let's verify we can call more than once:
        sw.close();
        sw.close();
        assertTrue(output.isClosed());

        // Then implicit close:
        output = new MyWriter();
        sw = (XMLStreamWriter2) f.createXMLStreamWriter(output);
        writeDoc(sw);
        assertTrue(output.isClosed());
    }

    /**
     * This unit test checks what happens when we use Result abstraction
     * for passing in result stream/writer. Their handling differs depending
     * on whether caller is considered to have access to the underlying
     * physical object or not.
     */
    public void testAutoCloseImplicit()
        throws XMLStreamException
    {
        XMLOutputFactory2 f = getFactory(false); // auto-close disabled

        /* Ok, first: with regular (OutputStream, Writer) results not auto-closing
         * because caller does have access: StreamResult does retain given
         * stream/writer as is.
         */
        MyResult output = new MyResult();
        XMLStreamWriter2 sw = (XMLStreamWriter2) f.createXMLStreamWriter(output);
        assertFalse(output.isClosed());
        writeDoc(sw);
        sw.close();
        assertFalse(output.isClosed());

        /* And then more interesting case; verifying that Stax2Source
         * sub-classes are implicitly auto-closed: they need to be, because
         * they do not (necessarily) expose underlying physical stream.
         * We can test this by using any Stax2Source impl.
         */
        MyStringResult result = new MyStringResult();
        sw = (XMLStreamWriter2) f.createXMLStreamWriter(result);
        // closed if we write end doc
        writeDoc(sw);
        assertTrue(result.isClosed());

        // as well as if we just call regular close
        result = new MyStringResult();
        sw = (XMLStreamWriter2) f.createXMLStreamWriter(result);
        sw.writeStartDocument();
        sw.writeEmptyElement("test");
        // no call to write end doc, so writer can't yet close; but we do call close:
        sw.close();
        assertTrue(result.isClosed());
    }

    /*
    ////////////////////////////////////////
    // Non-test methods
    ////////////////////////////////////////
     */

    XMLOutputFactory2 getFactory(boolean autoClose)
    {
        XMLOutputFactory2 f = getOutputFactory();
        f.setProperty(XMLOutputFactory2.P_AUTO_CLOSE_OUTPUT, autoClose);
        return f;
    }

    void writeDoc(XMLStreamWriter sw) throws XMLStreamException
    {
        sw.writeStartDocument();
        sw.writeEmptyElement("root");
        sw.writeEndDocument();
    }

    /*
    ////////////////////////////////////////
    // Helper mock classes
    ////////////////////////////////////////
     */

    final static class MyWriter
        extends StringWriter
    {
        boolean mIsClosed = false;

        public MyWriter() { }

        @Override
        public void close() throws IOException {
            mIsClosed = true;
            super.close();
        }

        public boolean isClosed() { return mIsClosed; }
    }

    final static class MyStream
        extends ByteArrayOutputStream
    {
        boolean mIsClosed = false;

        public MyStream() { }

        @Override
        public void close() throws IOException {
            mIsClosed = true;
            super.close();
        }
        public boolean isClosed() { return mIsClosed; }
    }

    final static class MyResult
        extends StreamResult
    {
        final MyWriter mWriter;

        MyResult() {
            super();
            mWriter = new MyWriter();
            setWriter(mWriter);
        }

        public boolean isClosed() {
            return mWriter.isClosed();
        }
    }

    /**
     * Need a helper class to verify whether resources (OutputStream, Writer)
     * created via Stax2Result instances are (auto)closed or not.
     */
    private final static class MyStringResult
        extends Stax2BlockResult
    {
        MyWriter mWriter;

        public MyStringResult() { super(); }

        @Override
        public Writer constructWriter() {
            mWriter = new MyWriter();
            return mWriter;
        }
        @Override
        public OutputStream constructOutputStream() { return null; }

        public boolean isClosed() { return mWriter.isClosed(); }
    }
}