/*
 * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xml.internal.serializer;

import java.util.ArrayList;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.LexicalHandler;

/**
 * This class is used to provide a base behavior to be inherited
 * by other To...SAXHandler serializers.
 *
 * This class is not a public API.
 *
 * @xsl.usage internal
 */
public abstract class ToSAXHandler extends SerializerBase {
    public ToSAXHandler() { }

    public ToSAXHandler(ContentHandler hdlr, LexicalHandler lex, String encoding) {
        setContentHandler(hdlr);
        setLexHandler(lex);
        setEncoding(encoding);
    }

    public ToSAXHandler(ContentHandler handler, String encoding) {
        setContentHandler(handler);
        setEncoding(encoding);
    }

    /**
     * Underlying SAX handler. Taken from XSLTC
     */
    protected ContentHandler m_saxHandler;

    /**
     * Underlying LexicalHandler. Taken from XSLTC
     */
    protected LexicalHandler m_lexHandler;

    /**
     * A startPrefixMapping() call on a ToSAXHandler will pass that call
     * on to the wrapped ContentHandler, but should we also mirror these calls
     * with matching attributes, if so this field is true.
     * For example if this field is true then a call such as
     * startPrefixMapping("prefix1","uri1") will also cause the additional
     * internally generated attribute xmlns:prefix1="uri1" to be effectively added
     * to the attributes passed to the wrapped ContentHandler.
     */
    private boolean m_shouldGenerateNSAttribute = true;

    /** If this is true, then the content handler wrapped by this
     * serializer implements the TransformState interface which
     * will give the content handler access to the state of
     * the transform. */
    protected TransformStateSetter m_state = null;

    /**
     * Pass callback to the SAX Handler
     */
    protected void startDocumentInternal() throws SAXException {
        if (m_needToCallStartDocument) {
            super.startDocumentInternal();
            m_saxHandler.startDocument();
            m_needToCallStartDocument = false;
        }
    }

    /**
     * Do nothing.
     * @see org.xml.sax.ext.LexicalHandler#startDTD(String, String, String)
     */
    public void startDTD(String arg0, String arg1, String arg2)
        throws SAXException
    {
        // do nothing for now
    }

    /**
     * Receive notification of character data.
     *
     * @param chars The string of characters to process.
     *
     * @throws org.xml.sax.SAXException
     *
     * @see ExtendedContentHandler#characters(String)
     */
    public void characters(String chars) throws SAXException {
        final int len = (chars == null) ? 0 : chars.length();
        if (len > m_charsBuff.length) {
            m_charsBuff = new char[len * 2 + 1];
        }
        if (len > 0) {
            chars.getChars(0, len, m_charsBuff, 0);
        }
        characters(m_charsBuff, 0, len);
    }

    /**
     * Receive notification of a comment.
     *
     * @see ExtendedLexicalHandler#comment(String)
     */
    public void comment(String comment) throws SAXException {
        flushPending();

        // Ignore if a lexical handler has not been set
        if (m_lexHandler != null) {
            final int len = comment.length();
            if (len > m_charsBuff.length) {
               m_charsBuff = new char[len*2 + 1];
            }
            comment.getChars(0,len, m_charsBuff, 0);
            m_lexHandler.comment(m_charsBuff, 0, len);
            // time to fire off comment event
            if (m_tracer != null)
                super.fireCommentEvent(m_charsBuff, 0, len);
        }
    }

    /**
     * Do nothing as this is an abstract class. All subclasses will need to
     * define their behavior if it is different.
     * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
     */
    public void processingInstruction(String target, String data)
        throws SAXException
    {
        // Redefined in SAXXMLOutput
    }

    protected void closeStartTag() throws SAXException {
    }

    protected void closeCDATA() throws SAXException {
        // Redefined in SAXXMLOutput
    }

    /**
     * Receive notification of the beginning of an element, although this is a
     * SAX method additional namespace or attribute information can occur before
     * or after this call, that is associated with this element.
     *
     * @throws org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#startElement
     * @see org.xml.sax.ContentHandler#endElement
     * @see org.xml.sax.AttributeList
     *
     * @throws org.xml.sax.SAXException
     *
     * @see org.xml.sax.ContentHandler#startElement(String,String,String,Attributes)
     */
    public void startElement(String arg0, String arg1, String arg2,
                             Attributes arg3) throws SAXException
    {
        if (m_state != null) {
            m_state.resetState(getTransformer());
        }

        // fire off the start element event
        if (m_tracer != null)
            super.fireStartElem(arg2);
    }

    /**
     * Sets the LexicalHandler.
     * @param _lexHandler The LexicalHandler to set
     */
    public void setLexHandler(LexicalHandler _lexHandler) {
        this.m_lexHandler = _lexHandler;
    }

    /**
     * Sets the SAX ContentHandler.
     * @param _saxHandler The ContentHandler to set
     */
    public void setContentHandler(ContentHandler _saxHandler) {
        this.m_saxHandler = _saxHandler;
        if (m_lexHandler == null && _saxHandler instanceof LexicalHandler) {
            // we are not overwriting an existing LexicalHandler, and _saxHandler
            // is also implements LexicalHandler, so lets use it
            m_lexHandler = (LexicalHandler) _saxHandler;
        }
    }

    /**
     * Does nothing. The setting of CDATA section elements has an impact on
     * stream serializers.
     * @see SerializationHandler#setCdataSectionElements(java.util.ArrayList<String>)
     */
    public void setCdataSectionElements(ArrayList<String> URI_and_localNames) {
        // do nothing
    }

    /** Set whether or not namespace declarations (e.g.
     * xmlns:foo) should appear as attributes of
     * elements
     * @param doOutputNSAttr whether or not namespace declarations
     * should appear as attributes
     */
    public void setShouldOutputNSAttr(boolean doOutputNSAttr) {
        m_shouldGenerateNSAttribute = doOutputNSAttr;
    }

    /**
     * Returns true if namespace declarations from calls such as
     * startPrefixMapping("prefix1","uri1") should
     * also be mirrored with self generated additional attributes of elements
     * that declare the namespace, for example the attribute xmlns:prefix1="uri1"
     */
    boolean getShouldOutputNSAttr() {
        return m_shouldGenerateNSAttribute;
    }

    /**
     * This method flushes any pending events, which can be startDocument()
     * closing the opening tag of an element, or closing an open CDATA section.
     */
    public void flushPending() throws SAXException {
            if (m_needToCallStartDocument) {
                startDocumentInternal();
                m_needToCallStartDocument = false;
            }

            if (m_elemContext.m_startTagOpen) {
                closeStartTag();
                m_elemContext.m_startTagOpen = false;
            }

            if (m_cdataTagOpen) {
                closeCDATA();
                m_cdataTagOpen = false;
            }
    }

    /**
     * Pass in a reference to a TransformState object, which
     * can be used during SAX ContentHandler events to obtain
     * information about he state of the transformation. This
     * method will be called  before each startDocument event.
     *
     * @param ts A reference to a TransformState object
     */
    public void setTransformState(TransformStateSetter ts) {
        this.m_state = ts;
    }

    /**
     * Receives notification that an element starts, but attributes are not
     * fully known yet.
     *
     * @param uri the URI of the namespace of the element (optional)
     * @param localName the element name, but without prefix (optional)
     * @param qName the element name, with prefix, if any (required)
     *
     * @see ExtendedContentHandler#startElement(String, String, String)
     */
    public void startElement(String uri, String localName, String qName)
        throws SAXException {

        if (m_state != null) {
            m_state.resetState(getTransformer());
        }

        // fire off the start element event
        if (m_tracer != null)
            super.fireStartElem(qName);
    }

    /**
     * An element starts, but attributes are not fully known yet.
     *
     * @param qName the element name, with prefix (if any).

     * @see ExtendedContentHandler#startElement(String)
     */
    public void startElement(String qName) throws SAXException {
        if (m_state != null) {
            m_state.resetState(getTransformer());
        }
        // fire off the start element event
        if (m_tracer != null)
            super.fireStartElem(qName);
    }

    /**
     * This method gets the node's value as a String and uses that String as if
     * it were an input character notification.
     * @param node the Node to serialize
     * @throws org.xml.sax.SAXException
     */
    public void characters(org.w3c.dom.Node node)
        throws org.xml.sax.SAXException
    {
        // remember the current node
        if (m_state != null) {
            m_state.setCurrentNode(node);
        }

        // Get the node's value as a String and use that String as if
        // it were an input character notification.
        String data = node.getNodeValue();
        if (data != null) {
            this.characters(data);
        }
    }

    /**
     * @see org.xml.sax.ErrorHandler#fatalError(SAXParseException)
     */
    public void fatalError(SAXParseException exc) throws SAXException {
        super.fatalError(exc);

        m_needToCallStartDocument = false;

        if (m_saxHandler instanceof ErrorHandler) {
            ((ErrorHandler)m_saxHandler).fatalError(exc);
        }
    }

    /**
     * @see org.xml.sax.ErrorHandler#error(SAXParseException)
     */
    public void error(SAXParseException exc) throws SAXException {
        super.error(exc);

        if (m_saxHandler instanceof ErrorHandler)
            ((ErrorHandler)m_saxHandler).error(exc);

    }

    /**
     * @see org.xml.sax.ErrorHandler#warning(SAXParseException)
     */
    public void warning(SAXParseException exc) throws SAXException {
        super.warning(exc);
        if (m_saxHandler instanceof ErrorHandler)
            ((ErrorHandler)m_saxHandler).warning(exc);
    }

    /**
     * Try's to reset the super class and reset this class for
     * re-use, so that you don't need to create a new serializer
     * (mostly for performance reasons).
     *
     * @return true if the class was successfuly reset.
     * @see Serializer#reset()
     */
    public boolean reset() {
        boolean wasReset = false;
        if (super.reset()) {
            resetToSAXHandler();
            wasReset = true;
        }
        return wasReset;
    }

    /**
     * Reset all of the fields owned by ToSAXHandler class
     *
     */
    private void resetToSAXHandler() {
        this.m_lexHandler = null;
        this.m_saxHandler = null;
        this.m_state = null;
        this.m_shouldGenerateNSAttribute = false;
    }

    /**
     * Add a unique attribute
     */
    public void addUniqueAttribute(String qName, String value, int flags)
        throws SAXException
    {
        addAttribute(qName, value);
    }
}
