/* Copyright (c) 2001-2024, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.jdbc;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLXML;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stax.StAXResult;
import javax.xml.transform.stax.StAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.ClosableByteArrayOutputStream;
import org.hsqldb.lib.StringConverter;

import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

/**
 * The mapping in the JavaTM programming language for the SQL XML type.
 * XML is a built-in type that stores an XML value
 * as a column value in a row of a database table.
 * By default, drivers implement an SQLXML object as
 * a logical pointer to the XML data
 * rather than the data itself.
 * An SQLXML object is valid for the duration of the transaction in which it was created.
 * <p>
 * The SQLXML interface provides methods for accessing the XML value
 * as a String, a Reader or Writer, or as a Stream.  The XML value
 * may also be accessed through a Source or set as a Result, which
 * are used with XML Parser APIs such as DOM, SAX, and StAX, as
 * well as with XSLT transforms and XPath evaluations.
 * <p>
 * Methods in the interfaces ResultSet, CallableStatement, and PreparedStatement,
 * such as getSQLXML allow a programmer to access an XML value.
 * In addition, this interface has methods for updating an XML value.
 * <p>
 * The XML value of the SQLXML instance may be obtained as a BinaryStream using
 * <pre>
 *   SQLXML sqlxml = resultSet.getSQLXML(column);
 *   InputStream binaryStream = sqlxml.getBinaryStream();
 * </pre>
 * For example, to parse an XML value with a DOM parser:
 * <pre>
 *   DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 *   Document result = parser.parse(binaryStream);
 * </pre>
 * or to parse an XML value with a SAX parser to your handler:
 * <pre>
 *   SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
 *   parser.parse(binaryStream, myHandler);
 * </pre>
 * or to parse an XML value with a StAX parser:
 * <pre>
 *   XMLInputFactory factory = XMLInputFactory.newInstance();
 *   XMLStreamReader streamReader = factory.createXMLStreamReader(binaryStream);
 * </pre>
 * <p>
 * Because databases may use an optimized representation for the XML,
 * accessing the value through getSource() and
 * setResult() can lead to improved processing performance
 * without serializing to a stream representation and parsing the XML.
 * <p>
 * For example, to obtain a DOM Document Node:
 * <pre>
 *   DOMSource domSource = sqlxml.getSource(DOMSource.class);
 *   Document document = (Document) domSource.getNode();
 * </pre>
 * or to set the value to a DOM Document Node to myNode:
 * <pre>
 *   DOMResult domResult = sqlxml.setResult(DOMResult.class);
 *   domResult.setNode(myNode);
 * </pre>
 * or, to send SAX events to your handler:
 * <pre>
 *   SAXSource saxSource = sqlxml.getSource(SAXSource.class);
 *   XMLReader xmlReader = saxSource.getXMLReader();
 *   xmlReader.setContentHandler(myHandler);
 *   xmlReader.parse(saxSource.getInputSource());
 * </pre>
 * or, to set the result value from SAX events:
 * <pre>
 *   SAXResult saxResult = sqlxml.setResult(SAXResult.class);
 *   ContentHandler contentHandler = saxResult.getXMLReader().getContentHandler();
 *   contentHandler.startDocument();
 *   // set the XML elements and attributes into the result
 *   contentHandler.endDocument();
 * </pre>
 * or, to obtain StAX events:
 * <pre>
 *   StAXSource staxSource = sqlxml.getSource(StAXSource.class);
 *   XMLStreamReader streamReader = staxSource.getXMLStreamReader();
 * </pre>
 * or, to set the result value from StAX events:
 * <pre>
 *   StAXResult staxResult = sqlxml.setResult(StAXResult.class);
 *   XMLStreamWriter streamWriter = staxResult.getXMLStreamWriter();
 * </pre>
 * or, to perform XSLT transformations on the XML value using the XSLT in xsltFile
 * output to file resultFile:
 * <pre>
 *   File xsltFile = new File("a.xslt");
 *   File myFile = new File("result.xml");
 *   Transformer xslt = TransformerFactory.newInstance().newTransformer(new StreamSource(xsltFile));
 *   Source source = sqlxml.getSource(null);
 *   Result result = new StreamResult(myFile);
 *   xslt.transform(source, result);
 * </pre>
 * or, to evaluate an XPath expression on the XML value:
 * <pre>
 *   XPath xpath = XPathFactory.newInstance().newXPath();
 *   DOMSource domSource = sqlxml.getSource(DOMSource.class);
 *   Document document = (Document) domSource.getNode();
 *   String expression = "/foo/@bar";
 *   String barValue = xpath.evaluate(expression, document);
 * </pre>
 * To set the XML value to be the result of an XSLT transform:
 * <pre>
 *   File sourceFile = new File("source.xml");
 *   Transformer xslt = TransformerFactory.newInstance().newTransformer(new StreamSource(xsltFile));
 *   Source streamSource = new StreamSource(sourceFile);
 *   Result result = sqlxml.setResult(null);
 *   xslt.transform(streamSource, result);
 * </pre>
 * Any Source can be transformed to a Result using the identity transform
 * specified by calling newTransformer():
 * <pre>
 *   Transformer identity = TransformerFactory.newInstance().newTransformer();
 *   Source source = sqlxml.getSource(null);
 *   File myFile = new File("result.xml");
 *   Result result = new StreamResult(myFile);
 *   identity.transform(source, result);
 * </pre>
 * To write the contents of a Source to standard output:
 * <pre>
 *   Transformer identity = TransformerFactory.newInstance().newTransformer();
 *   Source source = sqlxml.getSource(null);
 *   Result result = new StreamResult(System.out);
 *   identity.transform(source, result);
 * </pre>
 * To create a DOMSource from a DOMResult:
 * <pre>
 *    DOMSource domSource = new DOMSource(domResult.getNode());
 * </pre>
 * <p>
 * Incomplete or invalid XML values may cause an SQLException when
 * set or the exception may occur when execute() occurs.  All streams
 * must be closed before execute() occurs or an SQLException will be thrown.
 * <p>
 * Reading and writing XML values to or from an SQLXML object can happen at most once.
 * The conceptual states of readable and not readable determine if one
 * of the reading APIs will return a value or throw an exception.
 * The conceptual states of writable and not writable determine if one
 * of the writing APIs will set a value or throw an exception.
 * <p>
 * The state moves from readable to not readable once free() or any of the
 * reading APIs are called: getBinaryStream(), getCharacterStream(), getSource(), and getString().
 * Implementations may also change the state to not writable when this occurs.
 * <p>
 * The state moves from writable to not writable once free() or any of the
 * writing APIs are called: setBinaryStream(), setCharacterStream(), setResult(), and setString().
 * Implementations may also change the state to not readable when this occurs.
 * <p>
 * All methods on the {@code SQLXML} interface must be fully implemented if the
 * JDBC driver supports the data type.
 *
 * <!-- start release-specific documentation -->
 * <div class="ReleaseSpecificDocumentation">
 * <p class="rshead">HSQLDB-Specific Information:</p>
 *
 * Starting with HSQLDB 2.0, a rudimentary client-side SQLXML interface
 * implementation (this class) is supported for local use when the product is
 * built and run under JDK 1.6+ and the SQLXML instance is constructed as the
 * result of calling JDBCConnection.createSQLXML(). <p>
 *
 * SQLXML instances retrieved in such a fashion are initially write-only, with
 * the lifecycle of read and write availability constrained in accordance with
 * the documentation of the interface methods. <p>
 *
 * When build and run under JDK 1.6+, it is also possible to retrieve read-only
 * SQLXML instances from JDBCResultSet.getSQLXML(...), given that the underlying
 * data can be converted to an XML Document Object Model (DOM). <p>
 *
 * However, at the time of this writing (2007-06-12) it is not yet possible to
 * store SQLXML objects directly into an HSQLDB database or to use them directly
 * for HSQLDB statement parameterization purposes.  This is because the SQLXML
 * data type is not yet natively supported by the HSQLDB engine. Instead, a
 * JDBCSQLXML instance must first be read as a string, binary input stream,
 * character input stream and so on, which can then be used for such purposes. <p>
 *
 * Here is the current read/write availability lifecycle for JDBCSQLXML:
 *
 * <TABLE>
 *     <CAPTION> Lifecycle</CAPTION>
 *     <THEAD>
 *         <TR>
 *             <TH>
 *                 Origin
 *             </TH>
 *             <TH>
 *                 Initially
 *             </TH>
 *             <TH>
 *                 After 1<SUP>st</SUP> Write
 *             </TH>
 *             <TH>
 *                 After 1<SUP>st</SUP> Read
 *             </TH>
 *             <TH>
 *                 After 1<SUP>st</SUP> Free
 *             </TH>
 *         </TR>
 *     </THEAD>
 *     <TBODY>
 *         <TR >
 *             <TH>
 *                 {@code org.hsqldb.jdbc.JDBCConnection.createSQLXML()}
 *             </TH>
 *             <TD >
 *                 Write-only
 *             </TD>
 *             <TD>
 *                 Read-only
 *             </TD>
 *             <TD>
 *                 Not readable or writable
 *             </TD>
 *             <TD>
 *                 Not readable or writable
 *             </TD>
 *         </TR>
 *         <TR>
 *             <TH>
 *                 {@code org.hsqldb.jdbc.JDBCResultSet.getSQLXML(...)}
 *             </TH>
 *             <TD >
 *                 Read-only
 *             </TD>
 *             <TD>
 *                 N/A
 *             </TD>
 *             <TD>
 *                 Not readable or writable
 *             </TD>
 *             <TD>
 *                 Not readable or writable
 *             </TD>
 *         </TR>
 *     </TBODY>
 * </TABLE>
 * </div>
 * <!-- end release-specific documentation -->
 *
 * @author Campbell Burnet (campbell-burnet@users dot sourceforge.net)
 * @see javax.xml.parsers
 * @see javax.xml.stream
 * @see javax.xml.transform
 * @see javax.xml.xpath
 * @since JDK 1.6, HSQLDB 2.0
 */
public class JDBCSQLXML implements SQLXML {

    private static String domFeatures = "XML 3.0 Traversal +Events 2.0";
    private static DOMImplementation         domImplementation;
    private static DOMImplementationRegistry domImplementationRegistry;
    private static ThreadPoolExecutor        executorService;
    private static Transformer               identityTransformer;
    private static TransformerFactory        transformerFactory;

    /**
     * Precomputed Charset to reduce octet to character sequence conversion
     * charset lookup overhead.
     */
    private static final Charset utf8Charset = StandardCharsets.UTF_8;
    private static ArrayBlockingQueue<Runnable> workQueue;

    /**
     * When non-null, the SAX ContentHandler currently in use to build this
     * object's SQLXML value from a SAX event sequence.
     */
    private SAX2DOMBuilder builder;

    /**
     * Whether this object is closed.  When closed, no further reading
     * or writing is possible.
     */
    private boolean closed;

    // ------------------------- Internal Implementation -----------------------

    /**
     * This object's SQLXML value as a GZIPed byte array
     */
    private volatile byte[] gzdata;

    /**
     * When non-null, the stream currently in use to read this object's
     * SQLXML value as an octet sequence.
     */
    private InputStream inputStream;

    /**
     * When non-null, the stream currently in use to write this object's
     * SQLXML value from an octet sequence.
     */
    private ClosableByteArrayOutputStream outputStream;

    /**
     * When non-null, the DOMResult currently in use to build this object's
     * SQLXML value.
     */
    private DOMResult domResult;

    /**
     * This object's public id
     */
    private String publicId;

    /**
     * Whether it is possible to read this object's SQLXML value.
     */
    private boolean readable;

    /**
     * This object's system id
     */
    private String systemId;

    /**
     * Whether it is possible to write this object's SQLXML value.
     */
    private boolean writable;

    /**
     * Constructs a new, initially write-only JDBCSQLXML object.
     */
    protected JDBCSQLXML() {
        setReadable(false);
        setWritable(true);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given octet
     * sequence.
     *
     * @param bytes the octet sequence representing the SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(byte[] bytes) throws SQLException {
        this(bytes, null);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given character
     * sequence.
     *
     * @param chars the character sequence representing the SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(char[] chars) throws SQLException {
        this(chars, 0, chars.length, null);
    }

    /**
     * Constructs a new JDBCSQLXML object from the given Document.
     *
     * @param document the Document representing the SQLXML value
     * @throws java.sql.SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(Document document) throws SQLException {
        this(new DOMSource(document));
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given octet
     * sequence. <p>
     *
     * Relative URI references will be resolved against the present working
     * directory reported by the Java virtual machine.
     *
     * @param inputStream an octet stream representing an SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(InputStream inputStream) throws SQLException {
        this(inputStream, null);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given character
     * sequence. <p>
     *
     * Relative URI references will be resolved against the present working
     * directory reported by the Java virtual machine. <p>
     *
     * <b>Note:</b>Normally, a byte stream should be used rather than a reader,
     * so that the XML parser can resolve character encoding specified by the
     * XML declaration. However, in many cases the encoding of the input stream
     * is already resolved, as in the case of reading XML from a StringReader.
     *
     * @param reader a character stream representing an SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(Reader reader) throws SQLException {
        this(reader, null);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given Source
     * object.
     *
     * @param source a Source representing an SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    public JDBCSQLXML(Source source) throws SQLException {
        init(source);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given character
     * sequence. <p>
     *
     * Relative URI references will be resolved against the present working
     * directory reported by the Java virtual machine.
     *
     * @param string a character sequence representing an SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(String string) throws SQLException {
        this(new StreamSource(new StringReader(string)));
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given octet
     * sequence.
     *
     * @param bytes the octet sequence representing the SQLXML value
     * @param systemId must be a String that conforms to the URI syntax;
     *        allows relative URIs to be processed.
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(byte[] bytes, String systemId) throws SQLException {
        this(new StreamSource(new ByteArrayInputStream(bytes), systemId));
    }

    protected JDBCSQLXML(char[] chars, String systemId) throws SQLException {
        this(chars, 0, chars.length, systemId);
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given octet
     * sequence. <p>
     *
     * Relative URI references will be resolved against the given systemId.
     *
     * @param inputStream an octet stream representing an SQLXML value
     * @param systemId a String that conforms to the URI syntax, indicating
     *      the URI from which the XML data is being read, so that relative URI
     *      references can be resolved
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(
            InputStream inputStream,
            String systemId)
            throws SQLException {
        this(new StreamSource(inputStream, systemId));

        this.inputStream = inputStream;
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given character
     * sequence. <p>
     *
     * Relative URI references will be resolved against the given systemId. <p>
     *
     * <b>Note:</b>Normally, a byte stream should be used rather than a reader,
     * so that the XML parser can resolve character encoding specified by the
     * XML declaration. However, in many cases the encoding of the input stream
     * is already resolved, as in the case of reading XML from a StringReader.
     *
     * @param reader a character stream representing an SQLXML value;
     * @param systemId a String that conforms to the URI syntax, indicating
     *      the URI from which the XML data is being read, so that relative URI
     *      references can be resolved
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(Reader reader, String systemId) throws SQLException {
        this(new StreamSource(reader, systemId));
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given character
     * sequence. <p>
     *
     * Relative URI references will be resolved against the given systemId.
     *
     * @param string a character sequence representing an SQLXML value
     * @param systemId a String that conforms to the URI syntax, indicating
     *      the URI from which the XML data is being read, so that relative URI
     *      references can be resolved
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(String string, String systemId) throws SQLException {
        this(new StreamSource(new StringReader(string), systemId));
    }

    /**
     * Constructs a new read-only JDBCSQLXML object from the given gzipped octet
     * sequence.
     *
     * @param bytes the gzipped octet sequence representing the SQLXML value
     * @param clone whether to clone the given gzipped octet sequence
     * @param systemId id
     * @param publicId id
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected JDBCSQLXML(
            byte[] bytes,
            boolean clone,
            String systemId,
            String publicId)
            throws SQLException {

        this.setGZipData(clone
                         ? bytes.clone()
                         : bytes);

        this.systemId = systemId;
        this.publicId = publicId;
    }

    protected JDBCSQLXML(
            char[] chars,
            int offset,
            int length,
            String systemId)
            throws SQLException {
        this(
            new StreamSource(new CharArrayReader(chars, offset, length),
                             systemId));
    }

    /**
     * This method closes this object and releases the resources that it held.
     * The SQL XML object becomes invalid and neither readable nor writable
     * when this method is called.<p>
     *
     * After {@code free} has been called, any attempt to invoke a
     * method other than {@code free} will result in a {@code SQLException}
     * being thrown.  If {@code free} is called multiple times, the subsequent
     * calls to {@code free} are treated as a no-op.
     * @throws SQLException if there is an error freeing the XML value.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public void free() throws SQLException {
        close();
    }

    /**
     * Retrieves the XML value designated by this SQLXML instance as a stream.
     * The bytes of the input stream are interpreted according to appendix F of the XML 1.0 specification.
     * The behavior of this method is the same as ResultSet.getBinaryStream()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not readable when this method is called and
     * may also become not writable depending on implementation.
     *
     * @return a stream containing the XML data.
     * @throws SQLException if there is an error processing the XML value.
     *   An exception is thrown if the state is not readable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized InputStream getBinaryStream() throws SQLException {

        checkClosed();
        checkReadable();

        InputStream rval = getBinaryStreamImpl();

        setReadable(false);
        setWritable(false);

        return rval;
    }

    /**
     * Retrieves a stream that can be used to write the XML value that this SQLXML instance represents.
     * The stream begins at position 0.
     * The bytes of the stream are interpreted according to appendix F of the XML 1.0 specification
     * The behavior of this method is the same as ResultSet.updateBinaryStream()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not writeable when this method is called and
     * may also become not readable depending on implementation.
     *
     * @return a stream to which data can be written.
     * @throws SQLException if there is an error processing the XML value.
     *   An exception is thrown if the state is not writable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized OutputStream setBinaryStream() throws SQLException {

        checkClosed();
        checkWritable();

        OutputStream rval = setBinaryStreamImpl();

        setWritable(false);
        setReadable(true);

        return rval;
    }

    /**
     * Retrieves the XML value designated by this SQLXML instance as a java.io.Reader object.
     * The format of this stream is defined by org.xml.sax.InputSource,
     * where the characters in the stream represent the unicode code points for
     * XML according to section 2 and appendix B of the XML 1.0 specification.
     * Although an encoding declaration other than unicode may be present,
     * the encoding of the stream is unicode.
     * The behavior of this method is the same as ResultSet.getCharacterStream()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not readable when this method is called and
     * may also become not writable depending on implementation.
     *
     * @return a stream containing the XML data.
     * @throws SQLException if there is an error processing the XML value.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if the stream does not contain valid characters.
     *   An exception is thrown if the state is not readable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized Reader getCharacterStream() throws SQLException {

        checkClosed();
        checkReadable();

        Reader reader = getCharacterStreamImpl();

        setReadable(false);
        setWritable(false);

        return reader;
    }

    /**
     * Retrieves a stream to be used to write the XML value that this SQLXML instance represents.
     * The format of this stream is defined by org.xml.sax.InputSource,
     * where the characters in the stream represent the unicode code points for
     * XML according to section 2 and appendix B of the XML 1.0 specification.
     * Although an encoding declaration other than unicode may be present,
     * the encoding of the stream is unicode.
     * The behavior of this method is the same as ResultSet.updateCharacterStream()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not writable when this method is called and
     * may also become not readable depending on implementation.
     *
     * @return a stream to which data can be written.
     * @throws SQLException if there is an error processing the XML value.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if the stream does not contain valid characters.
     *   An exception is thrown if the state is not writable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized Writer setCharacterStream() throws SQLException {

        checkClosed();
        checkWritable();

        Writer writer = setCharacterStreamImpl();

        setReadable(true);
        setWritable(false);

        return writer;
    }

    /**
     * Returns a string representation of the XML value designated by this SQLXML instance.
     * The format of this String is defined by org.xml.sax.InputSource,
     * where the characters in the stream represent the unicode code points for
     * XML according to section 2 and appendix B of the XML 1.0 specification.
     * Although an encoding declaration other than unicode may be present,
     * the encoding of the String is unicode.
     * The behavior of this method is the same as ResultSet.getString()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not readable when this method is called and
     * may also become not writable depending on implementation.
     *
     * @return a string representation of the XML value designated by this SQLXML instance.
     * @throws SQLException if there is an error processing the XML value.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if the stream does not contain valid characters.
     *   An exception is thrown if the state is not readable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized String getString() throws SQLException {

        checkClosed();
        checkReadable();

        String value = getStringImpl();

        setReadable(false);
        setWritable(false);

        return value;
    }

    /**
     * Sets the XML value designated by this SQLXML instance to the given String representation.
     * The format of this String is defined by org.xml.sax.InputSource,
     * where the characters in the stream represent the unicode code points for
     * XML according to section 2 and appendix B of the XML 1.0 specification.
     * Although an encoding declaration other than unicode may be present,
     * the encoding of the String is unicode.
     * The behavior of this method is the same as ResultSet.updateString()
     * when the designated column of the ResultSet has a type java.sql.Types of SQLXML.
     * <p>
     * The SQL XML object becomes not writeable when this method is called and
     * may also become not readable depending on implementation.
     *
     * @param value the XML value
     * @throws SQLException if there is an error processing the XML value.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if the stream does not contain valid characters.
     *   An exception is thrown if the state is not writable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized void setString(String value) throws SQLException {

        if (value == null) {
            throw JDBCUtil.nullArgument("value");
        }

        checkWritable();
        setStringImpl(value);
        setReadable(true);
        setWritable(false);
    }

    /**
     * Returns a Source for reading the XML value designated by this SQLXML instance.
     * Sources are used as inputs to XML parsers and XSLT transformers.
     * <p>
     * Sources for XML parsers will have namespace processing on by default.
     * The systemID of the Source is implementation dependent.
     * <p>
     * The SQL XML object becomes not readable when this method is called and
     * may also become not writable depending on implementation.
     * <p>
     * Note that SAX is a callback architecture, so a returned
     * SAXSource should then be set with a content handler that will
     * receive the SAX events from parsing.  The content handler
     * will receive callbacks based on the contents of the XML.
     * <pre>
     *   SAXSource saxSource = sqlxml.getSource(SAXSource.class);
     *   XMLReader xmlReader = saxSource.getXMLReader();
     *   xmlReader.setContentHandler(myHandler);
     *   xmlReader.parse(saxSource.getInputSource());
     * </pre>
     *
     * @param sourceClass The class of the source, or null.
     * If the class is null, a vendor specific Source implementation will be returned.
     * The following classes are supported at a minimum:
     * <pre>
     *   javax.xml.transform.dom.DOMSource - returns a DOMSource
     *   javax.xml.transform.sax.SAXSource - returns a SAXSource
     *   javax.xml.transform.stax.StAXSource - returns a StAXSource
     *   javax.xml.transform.stream.StreamSource - returns a StreamSource
     * </pre>
     * @return a Source for reading the XML value.
     * @throws SQLException if there is an error processing the XML value
     *   or if this feature is not supported.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if an XML parser exception occurs.
     *   An exception is thrown if the state is not readable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    @SuppressWarnings("unchecked")
    public synchronized <T extends Source> T getSource(
            Class<T> sourceClass)
            throws SQLException {

        checkClosed();
        checkReadable();

        final Source source = getSourceImpl(sourceClass);

        setReadable(false);
        setWritable(false);

        return (T) source;
    }

    /**
     * Returns a Result for setting the XML value designated by this SQLXML instance.
     * <p>
     * The systemID of the Result is implementation dependent.
     * <p>
     * The SQL XML object becomes not writable when this method is called and
     * may also become not readable depending on implementation.
     * <p>
     * Note that SAX is a callback architecture and the returned
     * SAXResult has a content handler assigned that will receive the
     * SAX events based on the contents of the XML.  Call the content
     * handler with the contents of the XML document to assign the values.
     * <pre>
     *   SAXResult saxResult = sqlxml.getResult(SAXResult.class);
     *   ContentHandler contentHandler = saxResult.getXMLReader().getContentHandler();
     *   contentHandler.startDocument();
     *   // set the XML elements and attributes into the result
     *   contentHandler.endDocument();
     * </pre>
     *
     * @param resultClass The class of the result, or null.
     * If resultClass is null, a vendor specific Result implementation will be returned.
     * The following classes are supported at a minimum:
     * <pre>
     *   javax.xml.transform.dom.DOMResult - returns a DOMResult
     *   javax.xml.transform.sax.SAXResult - returns a SAXResult
     *   javax.xml.transform.stax.StAXResult - returns a StAXResult
     *   javax.xml.transform.stream.StreamResult - returns a StreamResult
     * </pre>
     * @return Returns a Result for setting the XML value.
     * @throws SQLException if there is an error processing the XML value
     *   or if this feature is not supported.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if an XML parser exception occurs.
     *   An exception is thrown if the state is not writable.
     * @throws SQLFeatureNotSupportedException if the JDBC driver does not support
     * this method
     * @since JDK 1.6
     */
    public synchronized <T extends Result> T setResult(
            Class<T> resultClass)
            throws SQLException {

        checkClosed();
        checkWritable();

        final T result = createResult(resultClass);

        setReadable(true);
        setWritable(false);

        return result;
    }

    /**
     * @return that may be used to perform processing asynchronously.
     */
    protected static ExecutorService getExecutorService() {

        if (JDBCSQLXML.executorService == null) {
            int      corePoolSize    = 1;
            int      maximumPoolSize = 10;
            long     keepAliveTime   = 1;
            TimeUnit unit            = TimeUnit.SECONDS;

            JDBCSQLXML.workQueue = new ArrayBlockingQueue<>(10);
            JDBCSQLXML.executorService = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue);
        }

        return executorService;
    }

    /**
     * @return with which to obtain xml transformer instances.
     * @throws java.sql.SQLException when unable to obtain a factory instance.
     */
    protected static TransformerFactory getTransformerFactory()
            throws SQLException {

        if (JDBCSQLXML.transformerFactory == null) {
            try {
                JDBCSQLXML.transformerFactory =
                    TransformerFactory.newInstance();
            } catch (TransformerFactoryConfigurationError ex) {
                throw Exceptions.transformFailed(ex);
            }
        }

        return JDBCSQLXML.transformerFactory;
    }

    /**
     * @return used to perform identity transforms
     * @throws java.sql.SQLException when unable to obtain the instance.
     */
    protected static Transformer getIdentityTransformer() throws SQLException {

        if (JDBCSQLXML.identityTransformer == null) {
            try {
                JDBCSQLXML.identityTransformer =
                    getTransformerFactory().newTransformer();
            } catch (TransformerConfigurationException ex) {
                throw Exceptions.transformFailed(ex);
            }
        }

        return JDBCSQLXML.identityTransformer;
    }

    /**
     * @return with which to construct DOM implementation instances.
     * @throws java.sql.SQLException when unable to obtain a factory instance.
     */
    protected static DOMImplementationRegistry getDOMImplementationRegistry()
            throws SQLException {

        if (domImplementationRegistry == null) {
            try {
                domImplementationRegistry =
                    DOMImplementationRegistry.newInstance();
            } catch (ClassCastException ex) {
                throw Exceptions.domInstantiation(ex);
            } catch (InstantiationException ex) {
                throw Exceptions.domInstantiation(ex);
            } catch (ClassNotFoundException ex) {
                throw Exceptions.domInstantiation(ex);
            } catch (IllegalAccessException ex) {
                throw Exceptions.domInstantiation(ex);
            }
        }

        return domImplementationRegistry;
    }

    /**
     * @return with which to create document instances.
     * @throws java.sql.SQLException when unable to obtain the DOM
     *         implementation instance.
     */
    protected static DOMImplementation getDOMImplementation()
            throws SQLException {

        if (domImplementation == null) {
            domImplementation =
                getDOMImplementationRegistry().getDOMImplementation(
                    domFeatures);
        }

        if (domImplementation == null) {
            Exception ex = new RuntimeException(
                "Not supported: " + domFeatures);

            throw Exceptions.domInstantiation(ex);
        }

        return domImplementation;
    }

    /**
     * @param namespaceURI of the document element to create or {@code null}.
     * @param qualifiedName of the document element to be created or {@code null}.
     * @param docType of document to be created or {@code null}.
     *   When {@code doctype} is not {@code null}, its
     *   {@code Node.ownerDocument} attribute is set to the document
     *   being created.
     * @return with its document element.
     *   If the {@code NamespaceURI}, {@code qualifiedName}, and
     *   {@code doctype} are {@code null}, the returned
     *   {@code Document} is empty with no document element.
     * @throws java.sql.SQLException wrapping any internal exception that occurs.
     * @see org.w3c.dom.DOMImplementation#createDocument(String,String,DocumentType)
     */
    protected static Document createDocument(
            String namespaceURI,
            String qualifiedName,
            DocumentType docType)
            throws SQLException {

        try {
            return getDOMImplementation().createDocument(
                namespaceURI,
                qualifiedName,
                docType);
        } catch (DOMException ex) {
            throw Exceptions.domInstantiation(ex);
        }
    }

    /**
     * @return that is empty with no document element.
     * @throws java.sql.SQLException wrapping any internal exception that occurs.
     */
    protected static Document createDocument() throws SQLException {
        return createDocument(null, null, null);
    }

    /**
     * Initializes this object's SQLXML value from the given Source
     * object.
     *
     * @param source the Source representing the SQLXML value
     * @throws SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected void init(Source source) throws SQLException {

        if (source == null) {
            throw JDBCUtil.nullArgument("source");
        }

        Transformer           transformer = JDBCSQLXML.getIdentityTransformer();
        StreamResult          result      = new StreamResult();
        ByteArrayOutputStream baos        = new ByteArrayOutputStream();
        GZIPOutputStream      gzos;

        try {
            gzos = new GZIPOutputStream(baos);
        } catch (IOException ex) {
            throw Exceptions.transformFailed(ex);
        }

        result.setOutputStream(gzos);

        try {
            transformer.transform(source, result);
        } catch (TransformerException ex) {
            throw Exceptions.transformFailed(ex);
        }

        try {
            gzos.close();
        } catch (IOException ex) {
            throw Exceptions.transformFailed(ex);
        }

        byte[] data = baos.toByteArray();

        setGZipData(data);
        setReadable(true);
        setWritable(false);
    }

    /**
     * Assigns this object's SQLXML value from the designated gzipped array
     * of bytes.
     *
     * @param data the SQLXML value
     * @throws java.sql.SQLException if the argument does not represent a
     *      valid SQLXML value
     */
    protected void setGZipData(byte[] data) throws SQLException {

        if (data == null) {
            throw JDBCUtil.nullArgument("data");
        }

        this.gzdata = data;
    }

    /**
     * Directly retrieves this object's present SQLMXL value as a gzipped
     * array of bytes. <p>
     *
     * May be null, empty or invalid.
     *
     * @return this object's SQLMXL value as a gzipped byte array
     */
    protected byte[] gZipData() {
        return this.gzdata;
    }

    /**
     * Retrieves this object's SQLXML value as a gzipped array of bytes,
     * possibly by terminating any in-progress write operations and converting
     * accumulated intermediate data.
     *
     * @throws java.sql.SQLException if an underlying I/O or transform
     *      error occurs
     * @return this object's SQLXML value
     */
    protected byte[] getGZipData() throws SQLException {

        byte[] bytes = gZipData();

        if (bytes != null) {
            return bytes;
        }

        if (this.domResult != null) {
            DOMSource source = new DOMSource(
                domResult.getNode(),
                domResult.getSystemId());
            OutputStream os     = setBinaryStreamImpl();
            StreamResult result = new StreamResult(os);

            try {
                JDBCSQLXML.identityTransformer.transform(source, result);
            } catch (TransformerException ex) {
                throw Exceptions.transformFailed(ex);
            }

            try {
                os.close();
            } catch (IOException ex) {
                throw Exceptions.transformFailed(ex);
            }
        }

        if (this.outputStream == null) {
            throw Exceptions.notReadable("No Data.");
        } else if (!this.outputStream.isClosed()) {
            throw Exceptions.notReadable(
                "Stream used for writing must be closed but is still open.");
        } else if (this.outputStream.isFreed()) {
            throw Exceptions.notReadable(
                "Stream used for writing was freed and is no longer valid.");
        }

        try {
            setGZipData(this.outputStream.toByteArray());

            return gZipData();
        } catch (IOException ex) {
            throw Exceptions.notReadable();
        } finally {
            this.freeOutputStream();
        }
    }

    /**
     * closes this object and releases the resources that it holds.
     */
    protected synchronized void close() {

        this.closed = true;

        setReadable(false);
        setWritable(false);
        freeOutputStream();
        freeInputStream();
        freeDomResult();

        this.gzdata = null;
    }

    /**
     * Closes the input stream, if any, currently in use to read this object's
     * SQLXML value and nullifies the stream reference to make it eligible
     * for subsequent garbage collection.
     */
    protected void freeInputStream() {

        if (this.inputStream != null) {
            try {
                this.inputStream.close();
            } catch (IOException ex) {

                // ex.printStackTrace();
            } finally {
                this.inputStream = null;
            }
        }
    }

    /**
     * Closes the output stream, if any, currently in use to write this object's
     * SQLXML value and nullifies the stream reference to make it eligible for
     * subsequent garbage collection.  The stream's data buffer reference may
     * also be nullified, in order to make it eligible for garbage collection
     * immediately, just in case an external client still holds a reference to
     * the output stream.
     */
    protected void freeOutputStream() {

        if (this.outputStream != null) {
            this.outputStream.free();

            this.outputStream = null;
        }
    }

    /**
     * Checks whether this object is closed (has been freed).
     *
     * @throws java.sql.SQLException if this object is closed.
     */
    protected synchronized void checkClosed() throws SQLException {
        if (this.closed) {
            throw Exceptions.inFreedState();
        }
    }

    /**
     * Checks whether this object is readable.
     *
     * @throws java.sql.SQLException if this object is not readable.
     */
    protected synchronized void checkReadable() throws SQLException {
        if (!this.isReadable()) {
            throw Exceptions.notReadable();
        }
    }

    /**
     * Assigns this object's readability status.
     *
     * @param readable if {@code true}, then readable; else not readable
     */
    protected synchronized void setReadable(boolean readable) {
        this.readable = readable;
    }

    /**
     * Checks whether this object is writable.
     *
     * @throws java.sql.SQLException if this object is not writable.
     */
    protected synchronized void checkWritable() throws SQLException {
        if (!this.isWritable()) {
            throw Exceptions.notWritable();
        }
    }

    /**
     * Assigns this object's writability status.
     *
     * @param writable if {@code true}, then writable; else not writable
     */
    protected synchronized void setWritable(boolean writable) {
        this.writable = writable;
    }

    /**
     * Retrieves the object's readability status.
     *
     * @return if {@code true}, then readable; else not readable
     */
    public synchronized boolean isReadable() {
        return this.readable;
    }

    /**
     * Retrieves the object's readability status.
     *
     * @return if {@code true}, then writable; else not writable
     */
    public synchronized boolean isWritable() {
        return this.writable;
    }

    /**
     * Retrieves a stream representing the XML value designated by this
     * SQLXML instance.
     *
     * @return a stream containing the XML data.
     * @throws SQLException if there is an error processing the XML value.
     */
    protected InputStream getBinaryStreamImpl() throws SQLException {

        try {
            byte[]               data = getGZipData();
            ByteArrayInputStream bais = new ByteArrayInputStream(data);

            return new GZIPInputStream(bais);
        } catch (IOException ex) {
            throw Exceptions.transformFailed(ex);
        }
    }

    /**
     * Retrieves a reader representing the XML value designated by this
     * SQLXML instance.
     *
     * @return a reader containing the XML data.
     * @throws SQLException if there is an error processing the XML value.
     */
    protected Reader getCharacterStreamImpl() throws SQLException {
        return new InputStreamReader(getBinaryStreamImpl());
    }

    /**
     * Retrieves a string representing the XML value designated by this
     * SQLXML instance.
     *
     * @return a string containing the XML data.
     * @throws SQLException if there is an error processing the XML value.
     */
    protected String getStringImpl() throws SQLException {

        try {
            return StringConverter.inputStreamToString(
                getBinaryStreamImpl(),
                "US-ASCII");
        } catch (IOException ex) {
            throw Exceptions.transformFailed(ex);
        }
    }

    /**
     * Retrieves a stream to completely write the XML value this SQLXML
     * instance represents.
     *
     * @return a stream to which the data can be written.
     * @throws SQLException if there is an error processing the XML value.
     */
    protected OutputStream setBinaryStreamImpl() throws SQLException {

        this.outputStream = new ClosableByteArrayOutputStream();

        try {
            return new GZIPOutputStream(this.outputStream);
        } catch (IOException ex) {
            this.outputStream = null;

            throw Exceptions.resultInstantiation(ex);
        }
    }

    /**
     * Retrieves a writer to completely write the XML value this SQLXML
     * instance represents.
     *
     * @return a writer to which the data can be written.
     * @throws SQLException if there is an error processing the XML value.
     *   The getCause() method of the exception may provide a more detailed exception, for example,
     *   if the stream does not contain valid characters.
     *   An exception is thrown if the state is not writable.
     * @since JDK 1.6
     */
    protected Writer setCharacterStreamImpl() throws SQLException {
        return new OutputStreamWriter(setBinaryStreamImpl());
    }

    /**
     * Sets the XML value designated by this SQLXML instance using the given
     * String representation.
     *
     * @param value the XML value
     * @throws SQLException if there is an error processing the XML value.
     */
    protected void setStringImpl(String value) throws SQLException {
        init(new StreamSource(new StringReader(value)));
    }

    /**
     * Returns a Source for reading the XML value designated by this SQLXML
     * instance.
     *
     * @param sourceClass The class of the source, or null.  If null, then a
     *      DOMSource is returned.
     * @return a Source for reading the XML value.
     * @throws SQLException if there is an error processing the XML value
     *   or if the given {@code sourceClass} is not supported.
     */
    protected <T extends Source> T getSourceImpl(
            Class<T> sourceClass)
            throws SQLException {

        if (StreamSource.class.isAssignableFrom(sourceClass)) {
            return createStreamSource(sourceClass);
        } else if ((sourceClass == null)
                   || DOMSource.class.isAssignableFrom(sourceClass)) {
            return createDOMSource(sourceClass);
        } else if (SAXSource.class.isAssignableFrom(sourceClass)) {
            return createSAXSource(sourceClass);
        } else if (StAXSource.class.isAssignableFrom(sourceClass)) {
            return createStAXSource(sourceClass);
        }

        throw JDBCUtil.invalidArgument("sourceClass: " + sourceClass);
    }

    /**
     * Retrieves a new StreamSource for reading the XML value designated by this
     * SQLXML instance.
     *
     * @param sourceClass The class of the source
     * @throws java.sql.SQLException if there is an error processing the XML
     *      value or if the given {@code sourceClass} is not supported.
     * @return a new StreamSource for reading the XML value designated by this
     *      SQLXML instance
     */
    @SuppressWarnings("unchecked")
    protected <T extends Source> T createStreamSource(
            Class<T> sourceClass)
            throws SQLException {

        StreamSource source;

        try {
            if (sourceClass == null) {
                source = new StreamSource();
            } else {
                source = (StreamSource) sourceClass.getDeclaredConstructor()
                                                   .newInstance();
            }
        } catch (SecurityException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        Reader reader = getCharacterStreamImpl();

        source.setReader(reader);

        return (T) source;
    }

    /**
     * Retrieves a new DOMSource for reading the XML value designated by this
     * SQLXML instance.
     *
     * @param sourceClass The class of the source
     * @throws java.sql.SQLException if there is an error processing the XML
     *      value or if the given {@code sourceClass} is not supported.
     * @return a new DOMSource for reading the XML value designated by this
     *      SQLXML instance
     */
    @SuppressWarnings("unchecked")
    protected <T extends Source> T createDOMSource(
            Class<T> sourceClass)
            throws SQLException {

        DOMSource source;

        try {
            if (sourceClass == null) {
                source = new DOMSource();
            } else {
                source = (DOMSource) sourceClass.getDeclaredConstructor()
                                                .newInstance();
            }
        } catch (SecurityException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        Transformer  transformer  = JDBCSQLXML.getIdentityTransformer();
        InputStream  stream       = this.getBinaryStreamImpl();
        StreamSource streamSource = new StreamSource();
        DOMResult    result       = new DOMResult();

        streamSource.setInputStream(stream);

        try {
            transformer.transform(streamSource, result);
        } catch (TransformerException ex) {
            throw Exceptions.transformFailed(ex);
        }

        source.setNode(result.getNode());
        source.setSystemId(result.getSystemId());

        return (T) source;
    }

    /**
     * Retrieves a new SAXSource for reading the XML value designated by this
     * SQLXML instance.
     *
     * @param sourceClass The class of the source
     * @throws java.sql.SQLException if there is an error processing the XML
     *      value or if the given {@code sourceClass} is not supported.
     * @return a new SAXSource for reading the XML value designated by this
     *      SQLXML instance
     */
    @SuppressWarnings("unchecked")
    protected <T extends Source> T createSAXSource(
            Class<T> sourceClass)
            throws SQLException {

        SAXSource source;

        try {
            if (sourceClass == null) {
                source = new SAXSource();
            } else {
                source = (SAXSource) sourceClass.getDeclaredConstructor()
                                                .newInstance();
            }
        } catch (SecurityException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        Reader      reader      = getCharacterStreamImpl();
        InputSource inputSource = new InputSource(reader);

        source.setInputSource(inputSource);

        return (T) source;
    }

    /**
     * Retrieves a new StAXSource for reading the XML value designated by this
     * SQLXML instance.
     *
     * @param sourceClass The class of the source
     * @throws java.sql.SQLException if there is an error processing the XML
     *      value or if the given {@code sourceClass} is not supported.
     * @return a new StAXSource for reading the XML value designated by this
     *      SQLXML instance
     */
    @SuppressWarnings("unchecked")
    protected <T extends Source> T createStAXSource(
            Class<T> sourceClass)
            throws SQLException {

        StAXSource      source      = null;
        Constructor     sourceCtor  = null;
        Reader          reader      = null;
        XMLInputFactory factory     = null;
        XMLEventReader  eventReader = null;

        try {
            factory = XMLInputFactory.newInstance();
        } catch (FactoryConfigurationError ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        try {
            sourceCtor = (sourceClass == null)
                         ? StAXSource.class.getConstructor(XMLEventReader.class)
                         : sourceClass.getConstructor(XMLEventReader.class);
        } catch (SecurityException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        reader = getCharacterStreamImpl();

        try {
            eventReader = factory.createXMLEventReader(reader);
        } catch (XMLStreamException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        try {
            source = (StAXSource) sourceCtor.newInstance(eventReader);
        } catch (SecurityException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (IllegalArgumentException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex.getTargetException());
        } catch (ClassCastException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        return (T) source;
    }

    /**
     * Retrieves a new Result for setting the XML value designated by this
     * SQLXML instance.
     *
     * @param resultClass The class of the result, or null.
     * @throws java.sql.SQLException if there is an error processing the XML
     *         value or the state is not writable
     * @return for setting the XML value designated by this SQLXML instance.
     */
    protected <T extends Result> T createResult(
            Class<T> resultClass)
            throws SQLException {

        checkWritable();
        setWritable(false);
        setReadable(true);

        if ((resultClass == null)
                || StreamResult.class.isAssignableFrom(resultClass)) {
            return createStreamResult(resultClass);
        } else if (DOMResult.class.isAssignableFrom(resultClass)) {
            return createDOMResult(resultClass);
        } else if (SAXResult.class.isAssignableFrom(resultClass)) {
            return createSAXResult(resultClass);
        } else if (StAXResult.class.isAssignableFrom(resultClass)) {
            return createStAXResult(resultClass);
        }

        throw JDBCUtil.invalidArgument("resultClass: " + resultClass);
    }

    /**
     * Retrieves a new StreamResult for setting the XML value designated by this
     * SQLXML instance.
     *
     * @param resultClass The class of the result, or null.
     * @throws java.sql.SQLException if there is an error processing the XML
     *         value
     * @return for setting the XML value designated by this SQLXML instance.
     */

//  @SuppressWarnings("unchecked")
    protected <T extends Result> T createStreamResult(
            Class<T> resultClass)
            throws SQLException {

        StreamResult result;

        try {
            if (resultClass == null) {
                result = new StreamResult();
            } else {
                result = (StreamResult) resultClass.getDeclaredConstructor()
                                                   .newInstance();
            }
        } catch (SecurityException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        OutputStream stream = setBinaryStreamImpl();

        result.setOutputStream(stream);

        return (T) result;
    }

    /**
     * Retrieves a new DOMResult for setting the XML value designated by this
     * SQLXML instance.
     *
     * @param resultClass The class of the result, or null.
     * @throws java.sql.SQLException if there is an error processing the XML
     *         value
     * @return for setting the XML value designated by this SQLXML instance.
     */
    @SuppressWarnings("unchecked")
    protected <T extends Result> T createDOMResult(
            Class<T> resultClass)
            throws SQLException {

        try {
            T result;

            if (resultClass == null) {
                result = (T) new DOMResult();
            } else {
                result = resultClass.getDeclaredConstructor().newInstance();
            }

            this.domResult = (DOMResult) result;

            return result;
        } catch (SecurityException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }
    }

    /**
     *  Retrieves a new SAXResult for setting the XML value designated by this
     *  SQLXML instance.
     *
     *  @param resultClass The class of the result, or null.
     *  @throws java.sql.SQLException if there is an error processing the XML
     *          value
     *  @return for setting the XML value designated by this SQLXML instance.
     */
    @SuppressWarnings("unchecked")
    protected <T extends Result> T createSAXResult(
            Class<T> resultClass)
            throws SQLException {

        SAXResult result = null;

        try {
            if (resultClass == null) {
                result = new SAXResult();
            } else {
                result = (SAXResult) resultClass.getDeclaredConstructor()
                                                .newInstance();
            }
        } catch (SecurityException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (ClassCastException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.sourceInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.sourceInstantiation(ex);
        }

        SAX2DOMBuilder handler = null;

        try {
            handler = new SAX2DOMBuilder();
        } catch (ParserConfigurationException ex) {
            throw Exceptions.resultInstantiation(ex);
        }

        this.domResult = new DOMResult();

        result.setHandler(handler);
        this.domResult.setNode(handler.getDocument());

        return (T) result;
    }

    /**
     *  Retrieves a new DOMResult for setting the XML value designated by this
     *  SQLXML instance.
     *
     *  @param resultClass The class of the result, or null.
     *  @throws java.sql.SQLException if there is an error processing the XML
     *          value
     *  @return for setting the XML value designated by this SQLXML instance.
     */
    @SuppressWarnings("unchecked")
    protected <T extends Result> T createStAXResult(
            Class<T> resultClass)
            throws SQLException {

        StAXResult result = null;

        try {
            this.domResult = new DOMResult(
                (new SAX2DOMBuilder()).getDocument());

            XMLOutputFactory factory = XMLOutputFactory.newInstance();
            XMLStreamWriter xmlStreamWriter = factory.createXMLStreamWriter(
                this.domResult);

            if (resultClass == null || resultClass == StAXResult.class) {
                result = new StAXResult(xmlStreamWriter);
            } else {
                Constructor ctor = resultClass.getConstructor(
                    XMLStreamWriter.class);

                result = (StAXResult) ctor.newInstance(xmlStreamWriter);
            }
        } catch (ParserConfigurationException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (SecurityException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (IllegalArgumentException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (IllegalAccessException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (InvocationTargetException ex) {
            throw Exceptions.resultInstantiation(ex.getTargetException());
        } catch (FactoryConfigurationError ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (InstantiationException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (NoSuchMethodException ex) {
            throw Exceptions.resultInstantiation(ex);
        } catch (XMLStreamException ex) {
            throw Exceptions.resultInstantiation(ex);
        }

        return (T) result;
    }

    protected void freeDomResult() {
        this.domResult = null;
    }

    /**
     * Basically just a namespace to isolate SQLXML exception generation
     */
    protected static class Exceptions {

        /**
         * Construction Disabled.
         */
        private Exceptions() {}

        /**
         *  Retrieves a new SQLXML DOM instantiation exception.
         *
         * @param cause of the exception
         */
        static SQLException domInstantiation(Throwable cause) {

            Exception ex = (cause instanceof Exception)
                           ? (Exception) cause
                           : new Exception(cause);

            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "SQLXML DOM instantiation failed: " + cause,
                ex);
        }

        /**
         * Retrieves a new SQLXML source instantiation exception.
         *
         * @param cause of the exception.
         * @return a new SQLXML source instantiation exception
         */
        static SQLException sourceInstantiation(Throwable cause) {

            Exception ex = (cause instanceof Exception)
                           ? (Exception) cause
                           : new Exception(cause);

            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "SQLXML Source instantiation failed: " + cause,
                ex);
        }

        /**
         * Retrieves a new SQLXML result instantiation exception.
         *
         * @param cause of the exception.
         * @return a new SQLXML result instantiation exception
         */
        static SQLException resultInstantiation(Throwable cause) {

            Exception ex = (cause instanceof Exception)
                           ? (Exception) cause
                           : new Exception(cause);

            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "SQLXML Result instantiation failed: " + cause,
                ex);
        }

        /**
         * Retrieves a new SQLXML parse failed exception.
         *
         * @param cause of the exception.
         * @return a new SQLXML parse failed exception
         */
        static SQLException parseFailed(Throwable cause) {

            Exception ex = (cause instanceof Exception)
                           ? (Exception) cause
                           : new Exception(cause);

            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "parse failed: " + cause,
                ex);
        }

        /**
         * Retrieves a new SQLXML transform failed exception.
         *
         * @param cause of the exception.
         * @return a new SQLXML parse failed exception
         */
        static SQLException transformFailed(Throwable cause) {

            Exception ex = (cause instanceof Exception)
                           ? (Exception) cause
                           : new Exception(cause);

            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "transform failed: " + cause,
                ex);
        }

        /**
         * Retrieves a new SQLXML not readable exception.
         *
         * @return a new SQLXML not readable exception
         */
        static SQLException notReadable() {
            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_IO_ERROR,
                "SQLXML in not readable state");
        }

        /**
         * Retrieves a new SQLXML not readable exception.
         *
         * @return a new SQLXML not readable exception
         */
        static SQLException notReadable(String reason) {
            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_IO_ERROR,
                "SQLXML in not readable state: " + reason);
        }

        /**
         * Retrieves a new SQLXML not writable exception.
         *
         * @return a new SQLXML not writable exception
         */
        static SQLException notWritable() {
            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_IO_ERROR,
                "SQLXML in not writable state");
        }

        /**
         * Currently unused.
         *
         * @return never
         */
        static SQLException directUpdateByLocatorNotSupported() {
            return JDBCUtil.sqlException(
                ErrorCode.X_0A000,
                "SQLXML direct update by locator");
        }

        /**
         * Retrieves a new SQLXML in freed state exception.
         *
         * @return a new SQLXML in freed state exception
         */
        static SQLException inFreedState() {
            return JDBCUtil.sqlException(
                ErrorCode.GENERAL_ERROR,
                "SQLXML in freed state");
        }
    }

    // -------------------------------------------------------------------------

    /**
     * Builds a DOM from SAX events.
     */
    protected static class SAX2DOMBuilder implements ContentHandler, Closeable {

        /**
         *
         */
        private boolean closed;

        /**
         *
         */
        private Element currentElement;

        // --------------------- internal implementation -----------------------

        /**
         *
         */
        private Node currentNode;

        /**
         *
         */
        private Document document;

        /**
         *
         */
        private Locator locator;

        /**
         * Creates a new instance of SAX2DOMBuilder, which creates
         * a new document. The document is available via
         * {@link #getDocument()}.
         * @throws javax.xml.parsers.ParserConfigurationException exception
         */
        public SAX2DOMBuilder() throws ParserConfigurationException {

            DocumentBuilderFactory documentBuilderFactory;
            DocumentBuilder        documentBuilder;

            documentBuilderFactory = DocumentBuilderFactory.newInstance();

            documentBuilderFactory.setValidating(false);
            documentBuilderFactory.setNamespaceAware(true);

            documentBuilder  = documentBuilderFactory.newDocumentBuilder();
            this.document    = documentBuilder.newDocument();
            this.currentNode = this.document;
        }

        /**
         * Receive an object for locating the origin of SAX document events.
         *
         * <p>SAX parsers are strongly encouraged (though not absolutely
         * required) to supply a locator: if it does so, it must supply
         * the locator to the application by invoking this method before
         * invoking any of the other methods in the ContentHandler
         * interface.</p>
         *
         * <p>The locator allows the application to determine the end
         * position of any document-related event, even if the parser is
         * not reporting an error.  Typically, the application will
         * use this information for reporting its own errors (such as
         * character content that does not match an application's
         * business rules).  The information returned by the locator
         * is probably not sufficient for use with a search engine.</p>
         *
         * <p>Note that the locator will return correct information only
         * during the invocation SAX event callbacks after
         * {@link #startDocument startDocument} returns and before
         * {@link #endDocument endDocument} is called.  The
         * application should not attempt to use it at any other time.</p>
         *
         * @param locator an object that can return the location of
         *                any SAX document event
         * @see org.xml.sax.Locator
         */
        public void setDocumentLocator(Locator locator) {
            this.locator = locator;
        }

        /**
         * Retrieves the Locator.
         * @return the Locator
         */
        public Locator getDocumentLocator() {
            return this.locator;
        }

        /**
         * Receive notification of the beginning of a document.
         *
         * <p>The SAX parser will invoke this method only once, before any
         * other event callbacks (except for {@link #setDocumentLocator
         * setDocumentLocator}).</p>
         *
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #endDocument
         */
        public void startDocument() throws SAXException {
            checkClosed();
        }

        /**
         * Receive notification of the end of a document.
         *
         * <p><strong>There is an apparent contradiction between the
         * documentation for this method and the documentation for {@link
         * org.xml.sax.ErrorHandler#fatalError}.  Until this ambiguity is
         * resolved in a future major release, clients should make no
         * assumptions about whether endDocument() will or will not be
         * invoked when the parser has reported a fatalError() or thrown
         * an exception.</strong></p>
         *
         * <p>The SAX parser will invoke this method only once, and it will
         * be the last method invoked during the parse.  The parser shall
         * not invoke this method until it has either abandoned parsing
         * (because of an unrecoverable error) or reached the end of
         * input.</p>
         *
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #startDocument
         */
        public void endDocument() throws SAXException {
            checkClosed();
            close();
        }

        /**
         * Begin the scope of a prefix-URI Namespace mapping.
         *
         * <p>The information from this event is not necessary for
         * normal Namespace processing: the SAX XML reader will
         * automatically replace prefixes for element and attribute
         * names when the {@code http://xml.org/sax/features/namespaces}
         * feature is <var>true</var> (the default).</p>
         *
         * <p>There are cases, however, when applications need to
         * use prefixes in character data or in attribute values,
         * where they cannot safely be expanded automatically; the
         * start/endPrefixMapping event supplies the information
         * to the application to expand prefixes in those contexts
         * itself, if necessary.</p>
         *
         * <p>Note that start/endPrefixMapping events are not
         * guaranteed to be properly nested relative to each other:
         * all startPrefixMapping events will occur immediately before the
         * corresponding {@link #startElement startElement} event,
         * and all {@link #endPrefixMapping endPrefixMapping}
         * events will occur immediately after the corresponding
         * {@link #endElement endElement} event,
         * but their order is not otherwise
         * guaranteed.</p>
         *
         * <p>There should never be start/endPrefixMapping events for the
         * "xml" prefix, since it is predeclared and immutable.</p>
         *
         * @param prefix the Namespace prefix being declared.
         *      An empty string is used for the default element namespace,
         *      which has no prefix.
         * @param uri the Namespace URI the prefix is mapped to
         * @throws org.xml.sax.SAXException the client may throw
         *            an exception during processing
         * @see #endPrefixMapping
         * @see #startElement
         */
        public void startPrefixMapping(
                String prefix,
                String uri)
                throws SAXException {
            checkClosed();
        }

        /**
         * End the scope of a prefix-URI mapping.
         *
         * <p>See {@link #startPrefixMapping startPrefixMapping} for
         * details.  These events will always occur immediately after the
         * corresponding {@link #endElement endElement} event, but the order of
         * {@code endPrefixMapping} events is not otherwise
         * guaranteed.</p>
         *
         * @param prefix the prefix that was being mapped.
         *      This is the empty string when a default mapping scope ends.
         * @throws org.xml.sax.SAXException the client may throw
         *            an exception during processing
         * @see #startPrefixMapping
         * @see #endElement
         */
        public void endPrefixMapping(String prefix) throws SAXException {
            checkClosed();
        }

        /**
         * Receive notification of the beginning of an element.
         *
         * <p>The Parser will invoke this method at the beginning of every
         * element in the XML document; there will be a corresponding
         * {@link #endElement endElement} event for every startElement event
         * (even when the element is empty). All of the element's content will be
         * reported, in order, before the corresponding endElement
         * event.</p>
         *
         * <p>This event allows up to three name components for each
         * element:</p>
         *
         * <ol>
         * <li>the Namespace URI;</li>
         * <li>the local name; and</li>
         * <li>the qualified (prefixed) name.</li>
         * </ol>
         *
         * <p>Any or all of these may be provided, depending on the
         * values of the <var>http://xml.org/sax/features/namespaces</var>
         * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
         * properties:</p>
         *
         * <ul>
         * <li>the Namespace URI and local name are required when
         * the namespaces property is <var>true</var> (the default), and are
         * optional when the namespaces property is <var>false</var> (if one is
         * specified, both must be);</li>
         * <li>the qualified name is required when the namespace-prefixes property
         * is <var>true</var>, and is optional when the namespace-prefixes property
         * is <var>false</var> (the default).</li>
         * </ul>
         *
         * <p>Note that the attribute list provided will contain only
         * attributes with explicit values (specified or defaulted):
         * #IMPLIED attributes will be omitted.  The attribute list
         * will contain attributes used for Namespace declarations
         * (xmlns* attributes) only if the
         * {@code http://xml.org/sax/features/namespace-prefixes}
         * property is true (it is false by default, and support for a
         * true value is optional).</p>
         *
         * <p>Like {@link #characters characters()}, attribute values may have
         * characters that need more than one {@code char} value.  </p>
         *
         * @param uri the Namespace URI, or the empty string if the
         *        element has no Namespace URI or if Namespace
         *        processing is not being performed
         * @param localName the local name (without prefix), or the
         *        empty string if Namespace processing is not being
         *        performed
         * @param qName the qualified name (with prefix), or the
         *        empty string if qualified names are not available
         * @param atts the attributes attached to the element.  If
         *        there are no attributes, it shall be an empty
         *        Attributes object.  The value of this object after
         *        startElement returns is undefined
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #endElement
         * @see org.xml.sax.Attributes
         * @see org.xml.sax.helpers.AttributesImpl
         */
        public void startElement(
                String uri,
                String localName,
                String qName,
                Attributes atts)
                throws SAXException {

            checkClosed();

            Element element;

            if ((uri == null) || (uri.isEmpty())) {
                element = getDocument().createElement(qName);
            } else {
                element = getDocument().createElementNS(uri, qName);
            }

            if (atts != null) {
                for (int i = 0; i < atts.getLength(); i++) {
                    String attrURI   = atts.getURI(i);
                    String attrQName = atts.getQName(i);
                    String attrValue = atts.getValue(i);

                    if ((attrURI == null) || attrURI.isEmpty()) {
                        element.setAttribute(attrQName, attrValue);
                    } else {
                        element.setAttributeNS(attrURI, attrQName, attrValue);
                    }
                }
            }

            getCurrentNode().appendChild(element);
            setCurrentNode(element);

            if (getCurrentElement() == null) {
                setCurrentElement(element);
            }
        }

        /**
         * Receive notification of the end of an element.
         *
         * <p>The SAX parser will invoke this method at the end of every
         * element in the XML document; there will be a corresponding
         * {@link #startElement startElement} event for every endElement
         * event (even when the element is empty).</p>
         *
         * <p>For information on the names, see startElement.</p>
         *
         * @param uri the Namespace URI, or the empty string if the
         *        element has no Namespace URI or if Namespace
         *        processing is not being performed
         * @param localName the local name (without prefix), or the
         *        empty string if Namespace processing is not being
         *        performed
         * @param qName the qualified XML name (with prefix), or the
         *        empty string if qualified names are not available
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void endElement(
                String uri,
                String localName,
                String qName)
                throws SAXException {
            checkClosed();
            setCurrentNode(getCurrentNode().getParentNode());
        }

        /**
         * Receive notification of character data.
         *
         * <p>The Parser will call this method to report each chunk of
         * character data.  SAX parsers may return all contiguous character
         * data in a single chunk, or they may split it into several
         * chunks; however, all of the characters in any single event
         * must come from the same external entity so that the Locator
         * provides useful information.</p>
         *
         * <p>The application must not attempt to read from the array
         * outside of the specified range.</p>
         *
         * <p>Individual characters may consist of more than one Java
         * {@code char} value.  There are two important cases where this
         * happens, because characters can't be represented in just sixteen bits.
         * In one case, characters are represented in a <em>Surrogate Pair</em>,
         * using two special Unicode values. Such characters are in the so-called
         * "Astral Planes", with a code point above U+FFFF.  A second case involves
         * composite characters, such as a base character combining with one or
         * more accent characters. </p>
         *
         * <p> Your code should not assume that algorithms using
         * {@code char}-at-a-time idioms will be working in character
         * units; in some cases they will split characters.  This is relevant
         * wherever XML permits arbitrary characters, such as attribute values,
         * processing instruction data, and comments as well as in data reported
         * from this method.  It's also generally relevant whenever Java code
         * manipulates internationalized text; the issue isn't unique to XML.</p>
         *
         * <p>Note that some parsers will report whitespace in element
         * content using the {@link #ignorableWhitespace ignorableWhitespace}
         * method rather than this one (validating parsers <em>must</em>
         * do so).</p>
         *
         * @param ch the characters from the XML document
         * @param start the start position in the array
         * @param length the number of characters to read from the array
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #ignorableWhitespace
         * @see org.xml.sax.Locator
         */
        public void characters(
                char[] ch,
                int start,
                int length)
                throws SAXException {

            checkClosed();

            Node   node = getCurrentNode().getLastChild();
            String s    = new String(ch, start, length);

            if ((node != null) && (node.getNodeType() == Node.TEXT_NODE)) {
                ((Text) node).appendData(s);
            } else {
                Text text = getDocument().createTextNode(s);

                getCurrentNode().appendChild(text);
            }
        }

        /**
         * Receive notification of ignorable whitespace in element content.
         *
         * <p>Validating Parsers must use this method to report each chunk
         * of whitespace in element content (see the W3C XML 1.0
         * recommendation, section 2.10): non-validating parsers may also
         * use this method if they are capable of parsing and using
         * content models.</p>
         *
         * <p>SAX parsers may return all contiguous whitespace in a single
         * chunk, or they may split it into several chunks; however, all of
         * the characters in any single event must come from the same
         * external entity, so that the Locator provides useful
         * information.</p>
         *
         * <p>The application must not attempt to read from the array
         * outside of the specified range.</p>
         *
         * @param ch the characters from the XML document
         * @param start the start position in the array
         * @param length the number of characters to read from the array
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #characters
         */
        public void ignorableWhitespace(
                char[] ch,
                int start,
                int length)
                throws SAXException {
            characters(ch, start, length);
        }

        /**
         * Receive notification of a processing instruction.
         *
         * <p>The Parser will invoke this method once for each processing
         * instruction found: note that processing instructions may occur
         * before or after the main document element.</p>
         *
         * <p>A SAX parser must never report an XML declaration (XML 1.0,
         * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
         * using this method.</p>
         *
         * <p>Like {@link #characters characters()}, processing instruction
         * data may have characters that need more than one {@code char}
         * value. </p>
         *
         * @param target the processing instruction target
         * @param data the processing instruction data, or null if
         *        none was supplied.  The data does not include any
         *        whitespace separating it from the target
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void processingInstruction(
                String target,
                String data)
                throws SAXException {

            checkClosed();

            ProcessingInstruction processingInstruction;

            processingInstruction = getDocument().createProcessingInstruction(
                target,
                data);

            getCurrentNode().appendChild(processingInstruction);
        }

        /**
         * Receive notification of a skipped entity.
         * This is not called for entity references within markup constructs
         * such as element start tags or markup declarations.  (The XML
         * recommendation requires reporting skipped external entities.
         * SAX also reports internal entity expansion/non-expansion, except
         * within markup constructs.)
         *
         * <p>The Parser will invoke this method each time the entity is
         * skipped.  Non-validating processors may skip entities if they
         * have not seen the declarations (because, for example, the
         * entity was declared in an external DTD subset).  All processors
         * may skip external entities, depending on the values of the
         * {@code http://xml.org/sax/features/external-general-entities}
         * and the
         * {@code http://xml.org/sax/features/external-parameter-entities}
         * properties.</p>
         *
         * @param name the name of the skipped entity.  If it is a
         *        parameter entity, the name will begin with '%', and if
         *        it is the external DTD subset, it will be the string
         *        "[dtd]"
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void skippedEntity(String name) throws SAXException {

            checkClosed();

            EntityReference entityReference =
                getDocument().createEntityReference(
                    name);

            getCurrentNode().appendChild(entityReference);
        }

        /**
         * Closes this DOMBuilder.
         */
        public void close() {
            this.closed = true;
        }

        /**
         * Frees the DOMBuilder.
         */
        public void free() {

            close();

            this.document       = null;
            this.currentElement = null;
            this.currentNode    = null;
            this.locator        = null;
        }

        /**
         * Retrieves whether this DOMBuilder is closed.
         *
         * @return boolean
         */
        public boolean isClosed() {
            return this.closed;
        }

        /**
         * Checks whether this DOMBuilder is closed.
         *
         * @throws SAXException if this DOMBuilder is closed.
         */
        protected void checkClosed() throws SAXException {
            if (isClosed()) {
                throw new SAXException("content handler is closed.");    // NOI18N
            }
        }

        /**
         * Retrieves the document.
         *
         * @return Document
         */
        public Document getDocument() {
            return this.document;
        }

        /**
         * Retrieves the current element.
         */
        protected Element getCurrentElement() {
            return this.currentElement;
        }

        /**
         * Assigns the current element.
         * @param element the element
         */
        protected void setCurrentElement(Element element) {
            this.currentElement = element;
        }

        /**
         * Retrieves the current node.
         */
        protected Node getCurrentNode() {
            return this.currentNode;
        }

        /**
         * Assigns the current node.
         * @param node the node
         */
        protected void setCurrentNode(Node node) {
            this.currentNode = node;
        }
    }

    /**
     * Writes to a {@link javax.xml.stream.XMLStreamWriter XMLStreamWriter}
     * from SAX events.
     */
    public static class SAX2XMLStreamWriter
            implements ContentHandler, Closeable {

        /**
         * Namespace declarations for an upcoming element.
         */
        private List<QualifiedName> namespaces = new ArrayList<>();

        /**
         * Whether this object is closed.
         */
        private boolean closed;

        /**
         * This object's SAX locator.
         */
        private Locator locator;

        /**
         * XML stream writer where events are pushed.
         */
        private XMLStreamWriter writer;

        /**
         * Constructs a new SAX2XMLStreamWriter that writes SAX events to the
         * designated XMLStreamWriter.
         *
         * @param writer the writer to which to write SAX events
         */
        public SAX2XMLStreamWriter(XMLStreamWriter writer) {

            if (writer == null) {
                throw new NullPointerException("writer");
            }

            this.writer = writer;
        }

        /**
         * Receive notification of the beginning of a document.
         *
         * <p>The SAX parser will invoke this method only once, before any
         * other event callbacks (except for {@link #setDocumentLocator
         * setDocumentLocator}).</p>
         *
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #endDocument
         */
        public void startDocument() throws SAXException {

            checkClosed();

            try {
                this.writer.writeStartDocument();
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Receive notification of the end of a document.
         *
         * <p><strong>There is an apparent contradiction between the
         * documentation for this method and the documentation for {@link
         * org.xml.sax.ErrorHandler#fatalError}.  Until this ambiguity is
         * resolved in a future major release, clients should make no
         * assumptions about whether endDocument() will or will not be
         * invoked when the parser has reported a fatalError() or thrown
         * an exception.</strong></p>
         *
         * <p>The SAX parser will invoke this method only once, and it will
         * be the last method invoked during the parse.  The parser shall
         * not invoke this method until it has either abandoned parsing
         * (because of an unrecoverable error) or reached the end of
         * input.</p>
         *
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #startDocument
         */
        public void endDocument() throws SAXException {

            checkClosed();

            try {
                this.writer.writeEndDocument();
                this.writer.flush();
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Receive notification of character data.
         *
         * <p>The Parser will call this method to report each chunk of
         * character data.  SAX parsers may return all contiguous character
         * data in a single chunk, or they may split it into several
         * chunks; however, all of the characters in any single event
         * must come from the same external entity so that the Locator
         * provides useful information.</p>
         *
         * <p>The application must not attempt to read from the array
         * outside of the specified range.</p>
         *
         * <p>Individual characters may consist of more than one Java
         * {@code char} value.  There are two important cases where this
         * happens, because characters can't be represented in just sixteen bits.
         * In one case, characters are represented in a <em>Surrogate Pair</em>,
         * using two special Unicode values. Such characters are in the so-called
         * "Astral Planes", with a code point above U+FFFF.  A second case involves
         * composite characters, such as a base character combining with one or
         * more accent characters. </p>
         *
         * <p> Your code should not assume that algorithms using
         * {@code char}-at-a-time idioms will be working in character
         * units; in some cases they will split characters.  This is relevant
         * wherever XML permits arbitrary characters, such as attribute values,
         * processing instruction data, and comments as well as in data reported
         * from this method.  It's also generally relevant whenever Java code
         * manipulates internationalized text; the issue isn't unique to XML.</p>
         *
         * <p>Note that some parsers will report whitespace in element
         * content using the {@link #ignorableWhitespace ignorableWhitespace}
         * method rather than this one (validating parsers <em>must</em>
         * do so).</p>
         *
         * @param ch the characters from the XML document
         * @param start the start position in the array
         * @param length the number of characters to read from the array
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #ignorableWhitespace
         * @see org.xml.sax.Locator
         */
        public void characters(
                char[] ch,
                int start,
                int length)
                throws SAXException {

            checkClosed();

            try {
                this.writer.writeCharacters(ch, start, length);
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Receive notification of the beginning of an element.
         *
         * <p>The Parser will invoke this method at the beginning of every
         * element in the XML document; there will be a corresponding
         * {@link #endElement endElement} event for every startElement event
         * (even when the element is empty). All of the element's content will be
         * reported, in order, before the corresponding endElement
         * event.</p>
         *
         * <p>This event allows up to three name components for each
         * element:</p>
         *
         * <ol>
         * <li>the Namespace URI;</li>
         * <li>the local name; and</li>
         * <li>the qualified (prefixed) name.</li>
         * </ol>
         *
         * <p>Any or all of these may be provided, depending on the
         * values of the <var>http://xml.org/sax/features/namespaces</var>
         * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
         * properties:</p>
         *
         * <ul>
         * <li>the Namespace URI and local name are required when
         * the namespaces property is <var>true</var> (the default), and are
         * optional when the namespaces property is <var>false</var> (if one is
         * specified, both must be);</li>
         * <li>the qualified name is required when the namespace-prefixes property
         * is <var>true</var>, and is optional when the namespace-prefixes property
         * is <var>false</var> (the default).</li>
         * </ul>
         *
         * <p>Note that the attribute list provided will contain only
         * attributes with explicit values (specified or defaulted):
         * #IMPLIED attributes will be omitted.  The attribute list
         * will contain attributes used for Namespace declarations
         * (xmlns* attributes) only if the
         * {@code http://xml.org/sax/features/namespace-prefixes}
         * property is true (it is false by default, and support for a
         * true value is optional).</p>
         *
         * <p>Like {@link #characters characters()}, attribute values may have
         * characters that need more than one {@code char} value.  </p>
         *
         * @param namespaceURI the Namespace URI, or the empty string if the
         *        element has no Namespace URI or if Namespace
         *        processing is not being performed
         * @param localName the local name (without prefix), or the
         *        empty string if Namespace processing is not being
         *        performed
         * @param qName the qualified name (with prefix), or the
         *        empty string if qualified names are not available
         * @param atts the attributes attached to the element.  If
         *        there are no attributes, it shall be an empty
         *        Attributes object.  The value of this object after
         *        startElement returns is undefined
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #endElement
         * @see org.xml.sax.Attributes
         * @see org.xml.sax.helpers.AttributesImpl
         */
        public void startElement(
                String namespaceURI,
                String localName,
                String qName,
                Attributes atts)
                throws SAXException {

            checkClosed();

            try {
                int    qi     = qName.indexOf(':');
                String prefix = (qi > 0)
                                ? qName.substring(0, qi)
                                : "";

                this.writer.writeStartElement(prefix, localName, namespaceURI);

                int length = namespaces.size();

                for (int i = 0; i < length; i++) {
                    QualifiedName ns = namespaces.get(i);

                    this.writer.writeNamespace(ns.prefix, ns.namespaceName);
                }

                namespaces.clear();

                length = atts.getLength();

                for (int i = 0; i < length; i++) {
                    this.writer.writeAttribute(
                        atts.getURI(i),
                        atts.getLocalName(i),
                        atts.getValue(i));
                }
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Receive notification of the end of an element.
         *
         * <p>The SAX parser will invoke this method at the end of every
         * element in the XML document; there will be a corresponding
         * {@link #startElement startElement} event for every endElement
         * event (even when the element is empty).</p>
         *
         * <p>For information on the names, see startElement.</p>
         *
         * @param namespaceURI the Namespace URI, or the empty string if the
         *        element has no Namespace URI or if Namespace
         *        processing is not being performed
         * @param localName the local name (without prefix), or the
         *        empty string if Namespace processing is not being
         *        performed
         * @param qName the qualified XML name (with prefix), or the
         *        empty string if qualified names are not available
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void endElement(
                String namespaceURI,
                String localName,
                String qName)
                throws SAXException {

            checkClosed();

            try {
                this.writer.writeEndElement();
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Begin the scope of a prefix-URI Namespace mapping.
         *
         * <p>The information from this event is not necessary for
         * normal Namespace processing: the SAX XML reader will
         * automatically replace prefixes for element and attribute
         * names when the {@code http://xml.org/sax/features/namespaces}
         * feature is <var>true</var> (the default).</p>
         *
         * <p>There are cases, however, when applications need to
         * use prefixes in character data or in attribute values,
         * where they cannot safely be expanded automatically; the
         * start/endPrefixMapping event supplies the information
         * to the application to expand prefixes in those contexts
         * itself, if necessary.</p>
         *
         * <p>Note that start/endPrefixMapping events are not
         * guaranteed to be properly nested relative to each other:
         * all startPrefixMapping events will occur immediately before the
         * corresponding {@link #startElement startElement} event,
         * and all {@link #endPrefixMapping endPrefixMapping}
         * events will occur immediately after the corresponding
         * {@link #endElement endElement} event,
         * but their order is not otherwise
         * guaranteed.</p>
         *
         * <p>There should never be start/endPrefixMapping events for the
         * "xml" prefix, since it is predeclared and immutable.</p>
         *
         * @param prefix the Namespace prefix being declared.
         *      An empty string is used for the default element namespace,
         *      which has no prefix.
         * @param uri the Namespace URI the prefix is mapped to
         * @throws org.xml.sax.SAXException the client may throw
         *            an exception during processing
         * @see #endPrefixMapping
         * @see #startElement
         */
        public void startPrefixMapping(
                String prefix,
                String uri)
                throws SAXException {

            checkClosed();

            try {
                this.writer.setPrefix(prefix, uri);
                namespaces.add(new QualifiedName(prefix, uri));
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * End the scope of a prefix-URI mapping.
         *
         * <p>See {@link #startPrefixMapping startPrefixMapping} for
         * details.  These events will always occur immediately after the
         * corresponding {@link #endElement endElement} event, but the order of
         * {@code endPrefixMapping} events is not otherwise
         * guaranteed.</p>
         *
         * @param prefix the prefix that was being mapped.
         *      This is the empty string when a default mapping scope ends.
         * @throws org.xml.sax.SAXException the client may throw
         *            an exception during processing
         * @see #startPrefixMapping
         * @see #endElement
         */
        public void endPrefixMapping(String prefix) throws SAXException {
            checkClosed();

            //
        }

        /**
         * Receive notification of ignorable whitespace in element content.
         *
         * <p>Validating Parsers must use this method to report each chunk
         * of whitespace in element content (see the W3C XML 1.0
         * recommendation, section 2.10): non-validating parsers may also
         * use this method if they are capable of parsing and using
         * content models.</p>
         *
         * <p>SAX parsers may return all contiguous whitespace in a single
         * chunk, or they may split it into several chunks; however, all of
         * the characters in any single event must come from the same
         * external entity, so that the Locator provides useful
         * information.</p>
         *
         * <p>The application must not attempt to read from the array
         * outside of the specified range.</p>
         *
         * @param ch the characters from the XML document
         * @param start the start position in the array
         * @param length the number of characters to read from the array
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         * @see #characters
         */
        public void ignorableWhitespace(
                char[] ch,
                int start,
                int length)
                throws SAXException {
            characters(ch, start, length);
        }

        /**
         * Receive notification of a processing instruction.
         *
         * <p>The Parser will invoke this method once for each processing
         * instruction found: note that processing instructions may occur
         * before or after the main document element.</p>
         *
         * <p>A SAX parser must never report an XML declaration (XML 1.0,
         * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
         * using this method.</p>
         *
         * <p>Like {@link #characters characters()}, processing instruction
         * data may have characters that need more than one {@code char}
         * value. </p>
         *
         * @param target the processing instruction target
         * @param data the processing instruction data, or null if
         *        none was supplied.  The data does not include any
         *        whitespace separating it from the target
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void processingInstruction(
                String target,
                String data)
                throws SAXException {

            checkClosed();

            try {
                this.writer.writeProcessingInstruction(target, data);
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        /**
         * Receive an object for locating the origin of SAX document events.
         *
         * <p>SAX parsers are strongly encouraged (though not absolutely
         * required) to supply a locator: if it does so, it must supply
         * the locator to the application by invoking this method before
         * invoking any of the other methods in the ContentHandler
         * interface.</p>
         *
         * <p>The locator allows the application to determine the end
         * position of any document-related event, even if the parser is
         * not reporting an error.  Typically, the application will
         * use this information for reporting its own errors (such as
         * character content that does not match an application's
         * business rules).  The information returned by the locator
         * is probably not sufficient for use with a search engine.</p>
         *
         * <p>Note that the locator will return correct information only
         * during the invocation SAX event callbacks after
         * {@link #startDocument startDocument} returns and before
         * {@link #endDocument endDocument} is called.  The
         * application should not attempt to use it at any other time.</p>
         *
         * @param locator an object that can return the location of
         *                any SAX document event
         * @see org.xml.sax.Locator
         */
        public void setDocumentLocator(Locator locator) {
            this.locator = locator;
        }

        /**
         * Retrieves the Locator.
         * @return the Locator
         */
        public Locator getDocumentLocator() {
            return this.locator;
        }

        /**
         * Receive notification of a skipped entity.
         * This is not called for entity references within markup constructs
         * such as element start tags or markup declarations.  (The XML
         * recommendation requires reporting skipped external entities.
         * SAX also reports internal entity expansion/non-expansion, except
         * within markup constructs.)
         *
         * <p>The Parser will invoke this method each time the entity is
         * skipped.  Non-validating processors may skip entities if they
         * have not seen the declarations (because, for example, the
         * entity was declared in an external DTD subset).  All processors
         * may skip external entities, depending on the values of the
         * {@code http://xml.org/sax/features/external-general-entities}
         * and the
         * {@code http://xml.org/sax/features/external-parameter-entities}
         * properties.</p>
         *
         * @param name the name of the skipped entity.  If it is a
         *        parameter entity, the name will begin with '%', and if
         *        it is the external DTD subset, it will be the string
         *        "[dtd]"
         * @throws org.xml.sax.SAXException any SAX exception, possibly
         *            wrapping another exception
         */
        public void skippedEntity(String name) throws SAXException {
            checkClosed();

            //
        }

        public void comment(
                char[] ch,
                int start,
                int length)
                throws SAXException {

            checkClosed();

            try {
                this.writer.writeComment(new String(ch, start, length));
            } catch (XMLStreamException e) {
                throw new SAXException(e);
            }
        }

        public XMLStreamWriter getWriter() {
            return this.writer;
        }

        protected List<QualifiedName> getNamespaces() {
            return this.namespaces;
        }

        /**
         * Closes this object.
         */
        public void close() throws IOException {

            if (!this.closed) {
                this.closed = true;

                try {
                    this.writer.close();
                } catch (XMLStreamException e) {
                    throw new IOException(e);
                } finally {
                    this.writer     = null;
                    this.locator    = null;
                    this.namespaces = null;
                }
            }
        }

        /**
         * Retrieves whether this object is closed.
         *
         * @return boolean
         */
        public boolean isClosed() {
            return this.closed;
        }

        /**
         * Checks whether this object is closed.
         *
         * @throws SAXException if this DOMBuilder is closed.
         */
        protected void checkClosed() throws SAXException {
            if (isClosed()) {
                throw new SAXException("content handler is closed.");    // NOI18N
            }
        }

        // --------------------- internal implementation -----------------------
        protected static class QualifiedName {

            public final String namespaceName;
            public final String prefix;

            public QualifiedName(
                    final String prefix,
                    final String namespaceName) {
                this.prefix        = prefix;
                this.namespaceName = namespaceName;
            }
        }
    }
}
