/*
 * reserved comment block
 * DO NOT REMOVE OR ALTER!
 */
/*
 * 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.dom3;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;

import com.sun.org.apache.xml.internal.serializer.DOM3Serializer;
import com.sun.org.apache.xml.internal.serializer.Encodings;
import com.sun.org.apache.xml.internal.serializer.Serializer;
import com.sun.org.apache.xml.internal.serializer.ToXMLStream;
import com.sun.org.apache.xml.internal.serializer.OutputPropertiesFactory;
import com.sun.org.apache.xml.internal.serializer.SerializerFactory;
import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
import com.sun.org.apache.xml.internal.serializer.utils.Utils;
import com.sun.org.apache.xml.internal.serializer.utils.SystemIDResolver;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.DOMError;
import org.w3c.dom.DOMErrorHandler;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMStringList;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.ls.LSException;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.w3c.dom.ls.LSSerializerFilter;


/**
 * Implemenatation of DOM Level 3 org.w3c.ls.LSSerializer and
 * org.w3c.dom.ls.DOMConfiguration.  Serialization is achieved by delegating
 * serialization calls to <CODE>org.apache.xml.serializer.ToStream</CODE> or
 * one of its derived classes depending on the serialization method, while walking
 * the DOM in DOM3TreeWalker.
 * @see <a href="http://www.w3.org/TR/2004/REC-DOM-Level-3-LS-20040407/load-save.html#LS-LSSerializer">org.w3c.dom.ls.LSSerializer</a>
 * @see <a href="http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#DOMConfiguration">org.w3c.dom.DOMConfiguration</a>
 *
 * @version $Id:
 *
 * @xsl.usage internal
 */
final public class LSSerializerImpl implements DOMConfiguration, LSSerializer {

    /** private data members */
    private Serializer fXMLSerializer = null;

    // Tracks DOMConfiguration features.
    protected int fFeatures = 0;

    // Common DOM serializer
    private  DOM3Serializer fDOMSerializer = null;

    // A filter set on the LSSerializer
    private LSSerializerFilter fSerializerFilter = null;

    // Stores the nodeArg parameter to speed up multiple writes of the same node.
    private Node fVisitedNode = null;

    // The end-of-line character sequence used in serialization.  "\n" is whats used on the web.
    private String fEndOfLine = "\n";

    // The DOMErrorhandler.
    private DOMErrorHandler fDOMErrorHandler = null;

    // The Configuration parameter to pass to the Underlying serilaizer.
    private Properties fDOMConfigProperties = null;

    // The encoding to use during serialization.
    private String fEncoding;

    // ************************************************************************
    // DOM Level 3 DOM Configuration parameter names
    // ************************************************************************
    // Parameter canonical-form, true [optional] - NOT SUPPORTED
    private final static int CANONICAL = 0x1 << 0;

    // Parameter cdata-sections, true [required] (default)
    private final static int CDATA = 0x1 << 1;

    // Parameter check-character-normalization, true [optional] - NOT SUPPORTED
    private final static int CHARNORMALIZE = 0x1 << 2;

    // Parameter comments, true [required] (default)
    private final static int COMMENTS = 0x1 << 3;

    // Parameter datatype-normalization, true [optional] - NOT SUPPORTED
    private final static int DTNORMALIZE = 0x1 << 4;

    // Parameter element-content-whitespace, true [required] (default) - value - false [optional] NOT SUPPORTED
    private final static int ELEM_CONTENT_WHITESPACE = 0x1 << 5;

    // Parameter entities, true [required] (default)
    private final static int ENTITIES = 0x1 << 6;

    // Parameter infoset, true [required] (default), false has no effect --> True has no effect for the serializer
    private final static int INFOSET = 0x1 << 7;

    // Parameter namespaces, true [required] (default)
    private final static int NAMESPACES = 0x1 << 8;

    // Parameter namespace-declarations, true [required] (default)
    private final static int NAMESPACEDECLS = 0x1 << 9;

    // Parameter normalize-characters, true [optional] - NOT SUPPORTED
    private final static int NORMALIZECHARS = 0x1 << 10;

    // Parameter split-cdata-sections, true [required] (default)
    private final static int SPLITCDATA = 0x1 << 11;

    // Parameter validate, true [optional] - NOT SUPPORTED
    private final static int VALIDATE = 0x1 << 12;

    // Parameter validate-if-schema, true [optional] - NOT SUPPORTED
    private final static int SCHEMAVALIDATE = 0x1 << 13;

    // Parameter split-cdata-sections, true [required] (default)
    private final static int WELLFORMED = 0x1 << 14;

    // Parameter discard-default-content, true [required] (default)
    // Not sure how this will be used in level 2 Documents
    private final static int DISCARDDEFAULT = 0x1 << 15;

    // Parameter format-pretty-print, true [optional]
    private final static int PRETTY_PRINT = 0x1 << 16;

    // Parameter ignore-unknown-character-denormalizations, true [required] (default)
    // We currently do not support XML 1.1 character normalization
    private final static int IGNORE_CHAR_DENORMALIZE = 0x1 << 17;

    // Parameter discard-default-content, true [required] (default)
    private final static int XMLDECL = 0x1 << 18;
    // ************************************************************************

    // Recognized parameters for which atleast one value can be set
    private String fRecognizedParameters [] = {
            DOMConstants.DOM_CANONICAL_FORM,
            DOMConstants.DOM_CDATA_SECTIONS,
            DOMConstants.DOM_CHECK_CHAR_NORMALIZATION,
            DOMConstants.DOM_COMMENTS,
            DOMConstants.DOM_DATATYPE_NORMALIZATION,
            DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE,
            DOMConstants.DOM_ENTITIES,
            DOMConstants.DOM_INFOSET,
            DOMConstants.DOM_NAMESPACES,
            DOMConstants.DOM_NAMESPACE_DECLARATIONS,
            //DOMConstants.DOM_NORMALIZE_CHARACTERS,
            DOMConstants.DOM_SPLIT_CDATA,
            DOMConstants.DOM_VALIDATE,
            DOMConstants.DOM_VALIDATE_IF_SCHEMA,
            DOMConstants.DOM_WELLFORMED,
            DOMConstants.DOM_DISCARD_DEFAULT_CONTENT,
            DOMConstants.DOM_FORMAT_PRETTY_PRINT,
            DOMConstants.DOM_IGNORE_UNKNOWN_CHARACTER_DENORMALIZATIONS,
            DOMConstants.DOM_XMLDECL,
            DOMConstants.DOM_ERROR_HANDLER
    };


    /**
     * Constructor:  Creates a LSSerializerImpl object.  The underlying
     * XML 1.0 or XML 1.1 org.apache.xml.serializer.Serializer object is
     * created and initialized the first time any of the write methods are
     * invoked to serialize the Node.  Subsequent write methods on the same
     * LSSerializerImpl object will use the previously created Serializer object.
     */
    public LSSerializerImpl () {
        // set default parameters
        fFeatures |= CDATA;
        fFeatures |= COMMENTS;
        fFeatures |= ELEM_CONTENT_WHITESPACE;
        fFeatures |= ENTITIES;
        fFeatures |= NAMESPACES;
        fFeatures |= NAMESPACEDECLS;
        fFeatures |= SPLITCDATA;
        fFeatures |= WELLFORMED;
        fFeatures |= DISCARDDEFAULT;
        fFeatures |= XMLDECL;

        // New OutputFormat properties
        fDOMConfigProperties = new Properties();

        // Initialize properties to be passed on the underlying serializer
        initializeSerializerProps();

        // Read output_xml.properties and System Properties to initialize properties
        Properties  configProps = OutputPropertiesFactory.getDefaultMethodProperties("xml");

        // change xml version from 1.0 to 1.1
        //configProps.setProperty("version", "1.1");

        // Get a serializer that seriailizes according to the properties,
        // which in this case is to xml
        fXMLSerializer = new ToXMLStream();
        fXMLSerializer.setOutputFormat(configProps);

        // Initialize Serializer
        fXMLSerializer.setOutputFormat(fDOMConfigProperties);
    }

    /**
     * Initializes the underlying serializer's configuration depending on the
     * default DOMConfiguration parameters. This method must be called before a
     * node is to be serialized.
     *
     * @xsl.usage internal
     */
    public void initializeSerializerProps () {
        // canonical-form
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_CANONICAL_FORM, DOMConstants.DOM3_DEFAULT_FALSE);

        // cdata-sections
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_CDATA_SECTIONS, DOMConstants.DOM3_DEFAULT_TRUE);

        // "check-character-normalization"
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_CHECK_CHAR_NORMALIZATION,
                DOMConstants.DOM3_DEFAULT_FALSE);

        // comments
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_COMMENTS, DOMConstants.DOM3_DEFAULT_TRUE);

        // datatype-normalization
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_DATATYPE_NORMALIZATION,
                DOMConstants.DOM3_DEFAULT_FALSE);

        // element-content-whitespace
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE,
                DOMConstants.DOM3_DEFAULT_TRUE);

        // entities
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_ENTITIES, DOMConstants.DOM3_DEFAULT_TRUE);

        // error-handler
        // Should we set our default ErrorHandler
        /*
         * if (fDOMConfig.getParameter(Constants.DOM_ERROR_HANDLER) != null) {
         * fDOMErrorHandler =
         * (DOMErrorHandler)fDOMConfig.getParameter(Constants.DOM_ERROR_HANDLER); }
         */

        // infoset
        if ((fFeatures & INFOSET) != 0) {
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_NAMESPACES, DOMConstants.DOM3_DEFAULT_TRUE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_NAMESPACE_DECLARATIONS,
                    DOMConstants.DOM3_DEFAULT_TRUE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_COMMENTS, DOMConstants.DOM3_DEFAULT_TRUE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE,
                    DOMConstants.DOM3_DEFAULT_TRUE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_WELLFORMED, DOMConstants.DOM3_DEFAULT_TRUE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_ENTITIES, DOMConstants.DOM3_DEFAULT_FALSE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_CDATA_SECTIONS,
                    DOMConstants.DOM3_DEFAULT_FALSE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_VALIDATE_IF_SCHEMA,
                    DOMConstants.DOM3_DEFAULT_FALSE);
            fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                    + DOMConstants.DOM_DATATYPE_NORMALIZATION,
                    DOMConstants.DOM3_DEFAULT_FALSE);
        }

        // namespaces
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_NAMESPACES, DOMConstants.DOM3_DEFAULT_TRUE);

        // namespace-declarations
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_NAMESPACE_DECLARATIONS,
                DOMConstants.DOM3_DEFAULT_TRUE);

        // normalize-characters
        /*
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_NORMALIZE_CHARACTERS,
                DOMConstants.DOM3_DEFAULT_FALSE);
        */

        // split-cdata-sections
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_SPLIT_CDATA, DOMConstants.DOM3_DEFAULT_TRUE);

        // validate
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_VALIDATE, DOMConstants.DOM3_DEFAULT_FALSE);

        // validate-if-schema
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_VALIDATE_IF_SCHEMA,
                DOMConstants.DOM3_DEFAULT_FALSE);

        // well-formed
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_WELLFORMED, DOMConstants.DOM3_DEFAULT_TRUE);

        // pretty-print
        fDOMConfigProperties.setProperty(
                DOMConstants.S_XSL_OUTPUT_INDENT,
                DOMConstants.DOM3_DEFAULT_FALSE);
        fDOMConfigProperties.setProperty(
                OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, Integer.toString(4));

        //

        // discard-default-content
        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                + DOMConstants.DOM_DISCARD_DEFAULT_CONTENT,
                DOMConstants.DOM3_DEFAULT_TRUE);

        // xml-declaration
        fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL, "no");

    }

    // ************************************************************************
    // DOMConfiguraiton implementation
    // ************************************************************************

    /**
     * Checks if setting a parameter to a specific value is supported.
     *
     * @see org.w3c.dom.DOMConfiguration#canSetParameter(java.lang.String, java.lang.Object)
     * @since DOM Level 3
     * @param name A String containing the DOMConfiguration parameter name.
     * @param value An Object specifying the value of the corresponding parameter.
     */
    public boolean canSetParameter(String name, Object value) {
        if (value instanceof Boolean){
            if ( name.equalsIgnoreCase(DOMConstants.DOM_CDATA_SECTIONS)
                    || name.equalsIgnoreCase(DOMConstants.DOM_COMMENTS)
                    || name.equalsIgnoreCase(DOMConstants.DOM_ENTITIES)
                    || name.equalsIgnoreCase(DOMConstants.DOM_INFOSET)
                    || name.equalsIgnoreCase(DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE)
                    || name.equalsIgnoreCase(DOMConstants.DOM_NAMESPACES)
                    || name.equalsIgnoreCase(DOMConstants.DOM_NAMESPACE_DECLARATIONS)
                    || name.equalsIgnoreCase(DOMConstants.DOM_SPLIT_CDATA)
                    || name.equalsIgnoreCase(DOMConstants.DOM_WELLFORMED)
                    || name.equalsIgnoreCase(DOMConstants.DOM_DISCARD_DEFAULT_CONTENT)
                    || name.equalsIgnoreCase(DOMConstants.DOM_FORMAT_PRETTY_PRINT)
                    || name.equalsIgnoreCase(DOMConstants.DOM_XMLDECL)){
                // both values supported
                return true;
            }
            else if (name.equalsIgnoreCase(DOMConstants.DOM_CANONICAL_FORM)
                    || name.equalsIgnoreCase(DOMConstants.DOM_CHECK_CHAR_NORMALIZATION)
                    || name.equalsIgnoreCase(DOMConstants.DOM_DATATYPE_NORMALIZATION)
                    || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE_IF_SCHEMA)
                    || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE)
                    // || name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)
                    ) {
                // true is not supported
                return !((Boolean)value).booleanValue();
            }
            else if (name.equalsIgnoreCase(DOMConstants.DOM_IGNORE_UNKNOWN_CHARACTER_DENORMALIZATIONS)) {
                // false is not supported
                return ((Boolean)value).booleanValue();
            }
        }
        else if (name.equalsIgnoreCase(DOMConstants.DOM_ERROR_HANDLER) &&
                value == null || value instanceof DOMErrorHandler){
            return true;
        }
        return false;
    }
    /**
     * This method returns the value of a parameter if known.
     *
     * @see org.w3c.dom.DOMConfiguration#getParameter(java.lang.String)
     *
     * @param name A String containing the DOMConfiguration parameter name
     *             whose value is to be returned.
     * @return Object The value of the parameter if known.
     */
    public Object getParameter(String name) throws DOMException {

        if(name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)){
                      return null;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_COMMENTS)) {
            return ((fFeatures & COMMENTS) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_CDATA_SECTIONS)) {
            return ((fFeatures & CDATA) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_ENTITIES)) {
            return ((fFeatures & ENTITIES) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_NAMESPACES)) {
            return ((fFeatures & NAMESPACES) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_NAMESPACE_DECLARATIONS)) {
            return ((fFeatures & NAMESPACEDECLS) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_SPLIT_CDATA)) {
            return ((fFeatures & SPLITCDATA) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_WELLFORMED)) {
            return ((fFeatures & WELLFORMED) != 0) ? Boolean.TRUE : Boolean.FALSE;
        }  else if (name.equalsIgnoreCase(DOMConstants.DOM_DISCARD_DEFAULT_CONTENT)) {
            return ((fFeatures & DISCARDDEFAULT) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_FORMAT_PRETTY_PRINT)) {
            return ((fFeatures & PRETTY_PRINT) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_XMLDECL)) {
            return ((fFeatures & XMLDECL) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE)) {
            return ((fFeatures & ELEM_CONTENT_WHITESPACE) != 0) ? Boolean.TRUE : Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_IGNORE_UNKNOWN_CHARACTER_DENORMALIZATIONS)) {
            return Boolean.TRUE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_CANONICAL_FORM)
                || name.equalsIgnoreCase(DOMConstants.DOM_CHECK_CHAR_NORMALIZATION)
                || name.equalsIgnoreCase(DOMConstants.DOM_DATATYPE_NORMALIZATION)
                // || name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)
                || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE)
                || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE_IF_SCHEMA)) {
            return Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_INFOSET)){
            if ((fFeatures & ENTITIES) == 0 &&
                    (fFeatures & CDATA) == 0 &&
                    (fFeatures & ELEM_CONTENT_WHITESPACE) != 0 &&
                    (fFeatures & NAMESPACES) != 0 &&
                    (fFeatures & NAMESPACEDECLS) != 0 &&
                    (fFeatures & WELLFORMED) != 0 &&
                    (fFeatures & COMMENTS) != 0) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        } else if (name.equalsIgnoreCase(DOMConstants.DOM_ERROR_HANDLER)) {
            return fDOMErrorHandler;
        } else if (
                name.equalsIgnoreCase(DOMConstants.DOM_SCHEMA_LOCATION)
                || name.equalsIgnoreCase(DOMConstants.DOM_SCHEMA_TYPE)) {
            return null;
        } else {
            // Here we have to add the Xalan specific DOM Message Formatter
            String msg = Utils.messages.createMessage(
                    MsgKey.ER_FEATURE_NOT_FOUND,
                    new Object[] { name });
            throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
        }
    }

    /**
     * This method returns a of the parameters supported by this DOMConfiguration object
     * and for which at least one value can be set by the application
     *
     * @see org.w3c.dom.DOMConfiguration#getParameterNames()
     *
     * @return DOMStringList A list of DOMConfiguration parameters recognized
     *                       by the serializer
     */
    public DOMStringList getParameterNames() {
        return new DOMStringListImpl(fRecognizedParameters);
    }

    /**
     * This method sets the value of the named parameter.
     *
     * @see org.w3c.dom.DOMConfiguration#setParameter(java.lang.String, java.lang.Object)
     *
     * @param name A String containing the DOMConfiguration parameter name.
     * @param value An Object contaiing the parameters value to set.
     */
    public void setParameter(String name, Object value) throws DOMException {
        // If the value is a boolean
        if (value instanceof Boolean) {
            boolean state = ((Boolean) value).booleanValue();

            if (name.equalsIgnoreCase(DOMConstants.DOM_COMMENTS)) {
                fFeatures = state ? fFeatures | COMMENTS : fFeatures
                        & ~COMMENTS;
                // comments
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_COMMENTS, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_COMMENTS, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_CDATA_SECTIONS)) {
                fFeatures =  state ? fFeatures | CDATA : fFeatures
                        & ~CDATA;
                // cdata-sections
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_CDATA_SECTIONS, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_CDATA_SECTIONS, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_ENTITIES)) {
                fFeatures = state ? fFeatures | ENTITIES : fFeatures
                        & ~ENTITIES;
                // entities
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ENTITIES, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ENTITIES, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_NAMESPACES)) {
                fFeatures = state ? fFeatures | NAMESPACES : fFeatures
                        & ~NAMESPACES;
                // namespaces
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACES, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACES, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name
                    .equalsIgnoreCase(DOMConstants.DOM_NAMESPACE_DECLARATIONS)) {
                fFeatures = state ? fFeatures | NAMESPACEDECLS
                        : fFeatures & ~NAMESPACEDECLS;
                // namespace-declarations
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACE_DECLARATIONS, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACE_DECLARATIONS, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_SPLIT_CDATA)) {
                fFeatures = state ? fFeatures | SPLITCDATA : fFeatures
                        & ~SPLITCDATA;
                // split-cdata-sections
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_SPLIT_CDATA, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_SPLIT_CDATA, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_WELLFORMED)) {
                fFeatures = state ? fFeatures | WELLFORMED : fFeatures
                        & ~WELLFORMED;
                // well-formed
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_WELLFORMED, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_WELLFORMED, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name
                    .equalsIgnoreCase(DOMConstants.DOM_DISCARD_DEFAULT_CONTENT)) {
                fFeatures = state ? fFeatures | DISCARDDEFAULT
                        : fFeatures & ~DISCARDDEFAULT;
                // discard-default-content
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_DISCARD_DEFAULT_CONTENT, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_DISCARD_DEFAULT_CONTENT, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_FORMAT_PRETTY_PRINT)) {
                fFeatures = state ? fFeatures | PRETTY_PRINT : fFeatures
                        & ~PRETTY_PRINT;
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_INDENT,DOMConstants.DOM3_EXPLICIT_TRUE);
                    fDOMConfigProperties.setProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, Integer.toString(4));
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_INDENT,DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_XMLDECL)) {
                fFeatures = state ? fFeatures | XMLDECL : fFeatures
                        & ~XMLDECL;
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL, "no");
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL, "yes");
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE)) {
                fFeatures = state ? fFeatures | ELEM_CONTENT_WHITESPACE : fFeatures
                        & ~ELEM_CONTENT_WHITESPACE;
                // element-content-whitespace
                if (state) {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE, DOMConstants.DOM3_EXPLICIT_TRUE);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_IGNORE_UNKNOWN_CHARACTER_DENORMALIZATIONS)) {
                // false is not supported
                if (!state) {
                    // Here we have to add the Xalan specific DOM Message Formatter
                    String msg = Utils.messages.createMessage(
                            MsgKey.ER_FEATURE_NOT_SUPPORTED,
                            new Object[] { name });
                    throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
                } else {
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_IGNORE_UNKNOWN_CHARACTER_DENORMALIZATIONS, DOMConstants.DOM3_EXPLICIT_TRUE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_CANONICAL_FORM)
                    || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE_IF_SCHEMA)
                    || name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE)
                    || name.equalsIgnoreCase(DOMConstants.DOM_CHECK_CHAR_NORMALIZATION)
                    || name.equalsIgnoreCase(DOMConstants.DOM_DATATYPE_NORMALIZATION)
                    // || name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)
                    ) {
                // true is not supported
                if (state) {
                    String msg = Utils.messages.createMessage(
                            MsgKey.ER_FEATURE_NOT_SUPPORTED,
                            new Object[] { name });
                    throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
                } else {
                    if (name.equalsIgnoreCase(DOMConstants.DOM_CANONICAL_FORM)) {
                        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                                + DOMConstants.DOM_CANONICAL_FORM, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } else if (name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE_IF_SCHEMA)) {
                        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                                + DOMConstants.DOM_VALIDATE_IF_SCHEMA, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } else if (name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE)) {
                        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                                + DOMConstants.DOM_VALIDATE, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } else if (name.equalsIgnoreCase(DOMConstants.DOM_VALIDATE_IF_SCHEMA)) {
                        fDOMConfigProperties.setProperty(DOMConstants.DOM_CHECK_CHAR_NORMALIZATION
                                + DOMConstants.DOM_CHECK_CHAR_NORMALIZATION, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } else if (name.equalsIgnoreCase(DOMConstants.DOM_DATATYPE_NORMALIZATION)) {
                        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                                + DOMConstants.DOM_DATATYPE_NORMALIZATION, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } /* else if (name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)) {
                        fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                                + DOMConstants.DOM_NORMALIZE_CHARACTERS, DOMConstants.DOM3_EXPLICIT_FALSE);
                    } */
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_INFOSET)) {
                if (state) {
                    fFeatures &= ~ENTITIES;
                    fFeatures &= ~CDATA;
                    fFeatures &= ~SCHEMAVALIDATE;
                    fFeatures &= ~DTNORMALIZE;
                    fFeatures |= NAMESPACES;
                    fFeatures |= NAMESPACEDECLS;
                    fFeatures |= WELLFORMED;
                    fFeatures |= ELEM_CONTENT_WHITESPACE;
                    fFeatures |= COMMENTS;

                    // infoset
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACES, DOMConstants.DOM3_EXPLICIT_TRUE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_NAMESPACE_DECLARATIONS, DOMConstants.DOM3_EXPLICIT_TRUE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_COMMENTS, DOMConstants.DOM3_EXPLICIT_TRUE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ELEMENT_CONTENT_WHITESPACE, DOMConstants.DOM3_EXPLICIT_TRUE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_WELLFORMED, DOMConstants.DOM3_EXPLICIT_TRUE);

                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_ENTITIES, DOMConstants.DOM3_EXPLICIT_FALSE);

                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_CDATA_SECTIONS, DOMConstants.DOM3_EXPLICIT_FALSE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_VALIDATE_IF_SCHEMA, DOMConstants.DOM3_EXPLICIT_FALSE);
                    fDOMConfigProperties.setProperty(DOMConstants.S_DOM3_PROPERTIES_NS
                            + DOMConstants.DOM_DATATYPE_NORMALIZATION, DOMConstants.DOM3_EXPLICIT_FALSE);
                }
            } else if (name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)) {
                String msg = Utils.messages.createMessage(
                    MsgKey.ER_FEATURE_NOT_SUPPORTED,
                    new Object[] { name });
                throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
            } else {
                // Setting this to false has no effect
            }
        } // If the parameter value is not a boolean
        else if (name.equalsIgnoreCase(DOMConstants.DOM_ERROR_HANDLER)) {
            if (value == null || value instanceof DOMErrorHandler) {
                fDOMErrorHandler = (DOMErrorHandler)value;
            } else {
                String msg = Utils.messages.createMessage(
                        MsgKey.ER_TYPE_MISMATCH_ERR,
                        new Object[] { name });
                throw new DOMException(DOMException.TYPE_MISMATCH_ERR, msg);
            }
        } else if (
                name.equalsIgnoreCase(DOMConstants.DOM_SCHEMA_LOCATION)
                || name.equalsIgnoreCase(DOMConstants.DOM_SCHEMA_TYPE)
                || name.equalsIgnoreCase(DOMConstants.DOM_NORMALIZE_CHARACTERS)
                && value != null) {
            String msg = Utils.messages.createMessage(
                    MsgKey.ER_FEATURE_NOT_SUPPORTED,
                    new Object[] { name });
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, msg);
        } else {
            String msg = Utils.messages.createMessage(
                    MsgKey.ER_FEATURE_NOT_FOUND,
                    new Object[] { name });
            throw new DOMException(DOMException.NOT_FOUND_ERR, msg);
        }
    }
    // ************************************************************************


    // ************************************************************************
    // DOMConfiguraiton implementation
    // ************************************************************************

    /**
     * Returns the DOMConfiguration of the LSSerializer.
     *
     * @see org.w3c.dom.ls.LSSerializer#getDomConfig()
     * @since DOM Level 3
     * @return A DOMConfiguration object.
     */
    public DOMConfiguration getDomConfig() {
        return (DOMConfiguration)this;
    }

    /**
     * Returns the DOMConfiguration of the LSSerializer.
     *
     * @see org.w3c.dom.ls.LSSerializer#getFilter()
     * @since DOM Level 3
     * @return A LSSerializerFilter object.
     */
    public LSSerializerFilter getFilter() {
        return fSerializerFilter;
    }

    /**
     * Returns the End-Of-Line sequence of characters to be used in the XML
     * being serialized.  If none is set a default "\n" is returned.
     *
     * @see org.w3c.dom.ls.LSSerializer#getNewLine()
     * @since DOM Level 3
     * @return A String containing the end-of-line character sequence  used in
     * serialization.
     */
    public String getNewLine() {
        return fEndOfLine;
    }

    /**
     * Set a LSSerilizerFilter on the LSSerializer.  When set, the filter is
     * called before each node is serialized which depending on its implemention
     * determines if the node is to be serialized or not.
     *
     * @see org.w3c.dom.ls.LSSerializer#setFilter
     * @since DOM Level 3
     * @param filter A LSSerializerFilter to be applied to the stream to serialize.
     */
    public void setFilter(LSSerializerFilter filter) {
        fSerializerFilter = filter;
    }

    /**
     * Sets the End-Of-Line sequence of characters to be used in the XML
     * being serialized.  Setting this attribute to null will reset its
     * value to the default value i.e. "\n".
     *
     * @see org.w3c.dom.ls.LSSerializer#setNewLine
     * @since DOM Level 3
     * @param newLine a String that is the end-of-line character sequence to be used in
     * serialization.
     */
    public void setNewLine(String newLine) {
        fEndOfLine = newLine !=null? newLine: fEndOfLine;
    }

    /**
     * Serializes the specified node to the specified LSOutput and returns true if the Node
     * was successfully serialized.
     *
     * @see org.w3c.dom.ls.LSSerializer#write(org.w3c.dom.Node, org.w3c.dom.ls.LSOutput)
     * @since DOM Level 3
     * @param nodeArg The Node to serialize.
     * @throws org.w3c.dom.ls.LSException SERIALIZE_ERR: Raised if the
     * LSSerializer was unable to serialize the node.
     *
     */
    public boolean write(Node nodeArg, LSOutput destination) throws LSException {
        // If the destination is null
        if (destination == null) {
            String msg = Utils.messages
            .createMessage(
                    MsgKey.ER_NO_OUTPUT_SPECIFIED,
                    null);
            if (fDOMErrorHandler != null) {
                fDOMErrorHandler.handleError(new DOMErrorImpl(
                        DOMError.SEVERITY_FATAL_ERROR, msg,
                        MsgKey.ER_NO_OUTPUT_SPECIFIED));
            }
            throw new LSException(LSException.SERIALIZE_ERR, msg);
        }

        // If nodeArg is null, return false.  Should we throw and LSException instead?
        if (nodeArg == null ) {
            return false;
        }

        // Obtain a reference to the serializer to use
        // Serializer serializer = getXMLSerializer(xmlVersion);
        Serializer serializer = fXMLSerializer;
        serializer.reset();

        // If the node has not been seen
        if ( nodeArg != fVisitedNode) {
            // Determine the XML Document version of the Node
            String xmlVersion = getXMLVersion(nodeArg);

            // Determine the encoding: 1.LSOutput.encoding, 2.Document.inputEncoding, 3.Document.xmlEncoding.
            fEncoding = destination.getEncoding();
            if (fEncoding == null ) {
                fEncoding = getInputEncoding(nodeArg);
                fEncoding = fEncoding != null ? fEncoding : getXMLEncoding(nodeArg) == null? "UTF-8": getXMLEncoding(nodeArg);
            }

            // If the encoding is not recognized throw an exception.
            // Note: The serializer defaults to UTF-8 when created
            if (!Encodings.isRecognizedEncoding(fEncoding)) {
                String msg = Utils.messages
                .createMessage(
                        MsgKey.ER_UNSUPPORTED_ENCODING,
                        null);
                if (fDOMErrorHandler != null) {
                    fDOMErrorHandler.handleError(new DOMErrorImpl(
                            DOMError.SEVERITY_FATAL_ERROR, msg,
                            MsgKey.ER_UNSUPPORTED_ENCODING));
                }
                throw new LSException(LSException.SERIALIZE_ERR, msg);
            }

            serializer.getOutputFormat().setProperty("version", xmlVersion);

            // Set the output encoding and xml version properties
            fDOMConfigProperties.setProperty(DOMConstants.S_XERCES_PROPERTIES_NS + DOMConstants.S_XML_VERSION, xmlVersion);
            fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_ENCODING, fEncoding);

            // If the node to be serialized is not a Document, Element, or Entity
            // node
            // then the XML declaration, or text declaration, should be never be
            // serialized.
            if ( (nodeArg.getNodeType() != Node.DOCUMENT_NODE
                    || nodeArg.getNodeType() != Node.ELEMENT_NODE
                    || nodeArg.getNodeType() != Node.ENTITY_NODE)
                    && ((fFeatures & XMLDECL) != 0)) {
                fDOMConfigProperties.setProperty(
                        DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL,
                        DOMConstants.DOM3_DEFAULT_FALSE);
            }

            fVisitedNode = nodeArg;
        }

        // Update the serializer properties
        fXMLSerializer.setOutputFormat(fDOMConfigProperties);

        //
        try {

            // The LSSerializer will use the LSOutput object to determine
            // where to serialize the output to in the following order the
            // first one that is not null and not an empty string will be
            // used: 1.LSOutput.characterStream, 2.LSOutput.byteStream,
            // 3. LSOutput.systemId
            // 1.LSOutput.characterStream
            Writer writer = destination.getCharacterStream();
            if (writer == null ) {

                // 2.LSOutput.byteStream
                OutputStream outputStream = destination.getByteStream();
                if ( outputStream == null) {

                    // 3. LSOutput.systemId
                    String uri = destination.getSystemId();
                    if (uri == null) {
                        String msg = Utils.messages
                        .createMessage(
                                MsgKey.ER_NO_OUTPUT_SPECIFIED,
                                null);
                        if (fDOMErrorHandler != null) {
                            fDOMErrorHandler.handleError(new DOMErrorImpl(
                                    DOMError.SEVERITY_FATAL_ERROR, msg,
                                    MsgKey.ER_NO_OUTPUT_SPECIFIED));
                        }
                        throw new LSException(LSException.SERIALIZE_ERR, msg);

                    } else {
                        // Expand the System Id and obtain an absolute URI for it.
                        String absoluteURI = SystemIDResolver.getAbsoluteURI(uri);

                        URL url = new URL(absoluteURI);
                        OutputStream urlOutStream = null;
                        String protocol = url.getProtocol();
                        String host = url.getHost();

                        // For file protocols, there is no need to use a URL to get its
                        // corresponding OutputStream

                        // Scheme names consist of a sequence of characters. The lower case
                        // letters "a"--"z", digits, and the characters plus ("+"), period
                        // ("."), and hyphen ("-") are allowed. For resiliency, programs
                        // interpreting URLs should treat upper case letters as equivalent to
                        // lower case in scheme names (e.g., allow "HTTP" as well as "http").
                        if (protocol.equalsIgnoreCase("file")
                                && (host == null || host.length() == 0 || host.equals("localhost"))) {
                            // do we also need to check for host.equals(hostname)
                            urlOutStream = new FileOutputStream(new File(url.getPath()));

                        } else {
                            // This should support URL's whose schemes are mentioned in
                            // RFC1738 other than file

                            URLConnection urlCon = url.openConnection();
                            urlCon.setDoInput(false);
                            urlCon.setDoOutput(true);
                            urlCon.setUseCaches(false);
                            urlCon.setAllowUserInteraction(false);

                            // When writing to a HTTP URI, a HTTP PUT is performed.
                            if (urlCon instanceof HttpURLConnection) {
                                HttpURLConnection httpCon = (HttpURLConnection) urlCon;
                                httpCon.setRequestMethod("PUT");
                            }
                            urlOutStream = urlCon.getOutputStream();
                        }
                        // set the OutputStream to that obtained from the systemId
                        serializer.setWriter(new OutputStreamWriter(urlOutStream));
                    }
                } else {
                    // 2.LSOutput.byteStream
                    serializer.setWriter(new OutputStreamWriter(outputStream, fEncoding));
                }
            } else {
                // 1.LSOutput.characterStream
                serializer.setWriter(writer);
            }

            // The associated media type by default is set to text/xml on
            // org.apache.xml.serializer.SerializerBase.

            // Get a reference to the serializer then lets you serilize a DOM
            // Use this hack till Xalan support JAXP1.3
            if (fDOMSerializer == null) {
               fDOMSerializer = (DOM3Serializer)serializer.asDOM3Serializer();
            }

            // Set the error handler on the DOM3Serializer interface implementation
            if (fDOMErrorHandler != null) {
                fDOMSerializer.setErrorHandler(fDOMErrorHandler);
            }

            // Set the filter on the DOM3Serializer interface implementation
            if (fSerializerFilter != null) {
                fDOMSerializer.setNodeFilter(fSerializerFilter);
            }

            // Set the NewLine character to be used
            fDOMSerializer.setNewLine(fEndOfLine);

            // Serializer your DOM, where node is an org.w3c.dom.Node
            // Assuming that Xalan's serializer can serialize any type of DOM node
            fDOMSerializer.serializeDOM3(nodeArg);

        } catch( UnsupportedEncodingException ue) {

            String msg = Utils.messages
            .createMessage(
                    MsgKey.ER_UNSUPPORTED_ENCODING,
                    null);
            if (fDOMErrorHandler != null) {
                fDOMErrorHandler.handleError(new DOMErrorImpl(
                        DOMError.SEVERITY_FATAL_ERROR, msg,
                        MsgKey.ER_UNSUPPORTED_ENCODING, ue));
            }
            throw new LSException(LSException.SERIALIZE_ERR, ue.getMessage());
        } catch (LSException lse) {
            // Rethrow LSException.
            throw lse;
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e!=null?e.getMessage():"NULL Exception") ;
        }  catch (Exception e) {
            if (fDOMErrorHandler != null) {
                fDOMErrorHandler.handleError(new DOMErrorImpl(
                        DOMError.SEVERITY_FATAL_ERROR, e.getMessage(),
                        null, e));
            }
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e.toString());
        }
        return true;
    }

    /**
     * Serializes the specified node and returns a String with the serialized
     * data to the caller.
     *
     * @see org.w3c.dom.ls.LSSerializer#writeToString(org.w3c.dom.Node)
     * @since DOM Level 3
     * @param nodeArg The Node to serialize.
     * @throws org.w3c.dom.ls.LSException SERIALIZE_ERR: Raised if the
     * LSSerializer was unable to serialize the node.
     *
     */
    public String writeToString(Node nodeArg) throws DOMException, LSException {
        // return null is nodeArg is null.  Should an Exception be thrown instead?
        if (nodeArg == null) {
            return null;
        }

        // Should we reset the serializer configuration before each write operation?
        // Obtain a reference to the serializer to use
        Serializer serializer = fXMLSerializer;
        serializer.reset();

        if (nodeArg != fVisitedNode){
            // Determine the XML Document version of the Node
            String xmlVersion = getXMLVersion(nodeArg);

            serializer.getOutputFormat().setProperty("version", xmlVersion);

            // Set the output encoding and xml version properties
            fDOMConfigProperties.setProperty(DOMConstants.S_XERCES_PROPERTIES_NS + DOMConstants.S_XML_VERSION, xmlVersion);
            fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_ENCODING, "UTF-16");

            // If the node to be serialized is not a Document, Element, or Entity
            // node
            // then the XML declaration, or text declaration, should be never be
            // serialized.
            if  ((nodeArg.getNodeType() != Node.DOCUMENT_NODE
                    || nodeArg.getNodeType() != Node.ELEMENT_NODE
                    || nodeArg.getNodeType() != Node.ENTITY_NODE)
                    && ((fFeatures & XMLDECL) != 0)) {
                fDOMConfigProperties.setProperty(
                        DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL,
                        DOMConstants.DOM3_DEFAULT_FALSE);
            }

            fVisitedNode = nodeArg;
        }
        // Update the serializer properties
        fXMLSerializer.setOutputFormat(fDOMConfigProperties);

        // StringWriter to Output to
        StringWriter output = new StringWriter();

        //
        try {

            // Set the Serializer's Writer to a StringWriter
            serializer.setWriter(output);

            // Get a reference to the serializer then lets you serilize a DOM
            // Use this hack till Xalan support JAXP1.3
            if (fDOMSerializer == null) {
                fDOMSerializer = (DOM3Serializer)serializer.asDOM3Serializer();
            }

            // Set the error handler on the DOM3Serializer interface implementation
            if (fDOMErrorHandler != null) {
                fDOMSerializer.setErrorHandler(fDOMErrorHandler);
            }

            // Set the filter on the DOM3Serializer interface implementation
            if (fSerializerFilter != null) {
                fDOMSerializer.setNodeFilter(fSerializerFilter);
            }

            // Set the NewLine character to be used
            fDOMSerializer.setNewLine(fEndOfLine);

            // Serializer your DOM, where node is an org.w3c.dom.Node
            fDOMSerializer.serializeDOM3(nodeArg);
        } catch (LSException lse) {
            // Rethrow LSException.
            throw lse;
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e.toString());
        }  catch (Exception e) {
            if (fDOMErrorHandler != null) {
                fDOMErrorHandler.handleError(new DOMErrorImpl(
                        DOMError.SEVERITY_FATAL_ERROR, e.getMessage(),
                        null, e));
            }
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e.toString());
        }

        // return the serialized string
        return output.toString();
    }

    /**
     * Serializes the specified node to the specified URI and returns true if the Node
     * was successfully serialized.
     *
     * @see org.w3c.dom.ls.LSSerializer#writeToURI(org.w3c.dom.Node, String)
     * @since DOM Level 3
     * @param nodeArg The Node to serialize.
     * @throws org.w3c.dom.ls.LSException SERIALIZE_ERR: Raised if the
     * LSSerializer was unable to serialize the node.
     *
     */
    public boolean writeToURI(Node nodeArg, String uri) throws LSException {
        // If nodeArg is null, return false.  Should we throw and LSException instead?
        if (nodeArg == null ) {
            return false;
        }

        // Obtain a reference to the serializer to use
        Serializer serializer = fXMLSerializer;
        serializer.reset();

        if (nodeArg != fVisitedNode) {
            // Determine the XML Document version of the Node
            String xmlVersion = getXMLVersion(nodeArg);

            // Determine the encoding: 1.LSOutput.encoding,
            // 2.Document.inputEncoding, 3.Document.xmlEncoding.
            fEncoding = getInputEncoding(nodeArg);
            if (fEncoding == null ) {
                fEncoding = fEncoding != null ? fEncoding : getXMLEncoding(nodeArg) == null? "UTF-8": getXMLEncoding(nodeArg);
            }

            serializer.getOutputFormat().setProperty("version", xmlVersion);

            // Set the output encoding and xml version properties
            fDOMConfigProperties.setProperty(DOMConstants.S_XERCES_PROPERTIES_NS + DOMConstants.S_XML_VERSION, xmlVersion);
            fDOMConfigProperties.setProperty(DOMConstants.S_XSL_OUTPUT_ENCODING, fEncoding);

            // If the node to be serialized is not a Document, Element, or Entity
            // node
            // then the XML declaration, or text declaration, should be never be
            // serialized.
            if ( (nodeArg.getNodeType() != Node.DOCUMENT_NODE
                    || nodeArg.getNodeType() != Node.ELEMENT_NODE
                    || nodeArg.getNodeType() != Node.ENTITY_NODE)
                    && ((fFeatures & XMLDECL) != 0))  {
                fDOMConfigProperties.setProperty(
                        DOMConstants.S_XSL_OUTPUT_OMIT_XML_DECL,
                        DOMConstants.DOM3_DEFAULT_FALSE);
            }

            fVisitedNode = nodeArg;
        }

        // Update the serializer properties
        fXMLSerializer.setOutputFormat(fDOMConfigProperties);

        //
        try {
            // If the specified encoding is not supported an
            // "unsupported-encoding" fatal error is raised. ??
            if (uri == null) {
                String msg = Utils.messages.createMessage(
                        MsgKey.ER_NO_OUTPUT_SPECIFIED, null);
                if (fDOMErrorHandler != null) {
                    fDOMErrorHandler.handleError(new DOMErrorImpl(
                            DOMError.SEVERITY_FATAL_ERROR, msg,
                            MsgKey.ER_NO_OUTPUT_SPECIFIED));
                }
                throw new LSException(LSException.SERIALIZE_ERR, msg);

            } else {
                // REVISIT: Can this be used to get an absolute expanded URI
                String absoluteURI = SystemIDResolver.getAbsoluteURI(uri);

                URL url = new URL(absoluteURI);
                OutputStream urlOutStream = null;
                String protocol = url.getProtocol();
                String host = url.getHost();

                // For file protocols, there is no need to use a URL to get its
                // corresponding OutputStream

                // Scheme names consist of a sequence of characters. The lower
                // case letters "a"--"z", digits, and the characters plus ("+"),
                // period ("."), and hyphen ("-") are allowed. For resiliency,
                // programs interpreting URLs should treat upper case letters as
                // equivalent to lower case in scheme names
                // (e.g., allow "HTTP" as well as "http").
                if (protocol.equalsIgnoreCase("file")
                        && (host == null || host.length() == 0 || host
                                .equals("localhost"))) {
                    // do we also need to check for host.equals(hostname)
                    urlOutStream = new FileOutputStream(new File(url.getPath()));

                } else {
                    // This should support URL's whose schemes are mentioned in
                    // RFC1738 other than file

                    URLConnection urlCon = url.openConnection();
                    urlCon.setDoInput(false);
                    urlCon.setDoOutput(true);
                    urlCon.setUseCaches(false);
                    urlCon.setAllowUserInteraction(false);

                    // When writing to a HTTP URI, a HTTP PUT is performed.
                    if (urlCon instanceof HttpURLConnection) {
                        HttpURLConnection httpCon = (HttpURLConnection) urlCon;
                        httpCon.setRequestMethod("PUT");
                    }
                    urlOutStream = urlCon.getOutputStream();
                }
                // set the OutputStream to that obtained from the systemId
                serializer.setWriter(new OutputStreamWriter(urlOutStream, fEncoding));
            }

            // Get a reference to the serializer then lets you serilize a DOM
            // Use this hack till Xalan support JAXP1.3
            if (fDOMSerializer == null) {
                fDOMSerializer = (DOM3Serializer)serializer.asDOM3Serializer();
            }

            // Set the error handler on the DOM3Serializer interface implementation
            if (fDOMErrorHandler != null) {
                fDOMSerializer.setErrorHandler(fDOMErrorHandler);
            }

            // Set the filter on the DOM3Serializer interface implementation
            if (fSerializerFilter != null) {
                fDOMSerializer.setNodeFilter(fSerializerFilter);
            }

            // Set the NewLine character to be used
            fDOMSerializer.setNewLine(fEndOfLine);

            // Serializer your DOM, where node is an org.w3c.dom.Node
            // Assuming that Xalan's serializer can serialize any type of DOM
            // node
            fDOMSerializer.serializeDOM3(nodeArg);

        } catch (LSException lse) {
            // Rethrow LSException.
            throw lse;
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e.toString());
        }  catch (Exception e) {
            if (fDOMErrorHandler != null) {
                fDOMErrorHandler.handleError(new DOMErrorImpl(
                        DOMError.SEVERITY_FATAL_ERROR, e.getMessage(),
                        null, e));
            }
            e.printStackTrace();
            throw new LSException(LSException.SERIALIZE_ERR, e.toString());
        }

        return true;
    }
    // ************************************************************************


    // ************************************************************************
    // Implementaion methods
    // ************************************************************************

    /**
     * Determines the XML Version of the Document Node to serialize.  If the Document Node
     * is not a DOM Level 3 Node, then the default version returned is 1.0.
     *
     * @param  nodeArg The Node to serialize
     * @return A String containing the version pseudo-attribute of the XMLDecl.
     * @throws Throwable if the DOM implementation does not implement Document.getXmlVersion()
     */
    //protected String getXMLVersion(Node nodeArg) throws Throwable {
    protected String getXMLVersion(Node nodeArg) {
        Document doc = null;

        // Determine the XML Version of the document
        if (nodeArg != null) {
            if (nodeArg.getNodeType() == Node.DOCUMENT_NODE) {
                // The Document node is the Node argument
                doc = (Document)nodeArg;
            } else {
                // The Document node is the Node argument's ownerDocument
                doc = nodeArg.getOwnerDocument();
            }

            // Determine the DOM Version.
            if (doc != null && doc.getImplementation().hasFeature("Core","3.0")) {
                try {
                    return doc.getXmlVersion();
                } catch (AbstractMethodError e) {
                    //ignore, impl does not support the method
                }
            }
        }
        // The version will be treated as "1.0" which may result in
        // an ill-formed document being serialized.
        // If nodeArg does not have an ownerDocument, treat this as XML 1.0
        return "1.0";
    }

    /**
     * Determines the XML Encoding of the Document Node to serialize.  If the Document Node
     * is not a DOM Level 3 Node, then the default encoding "UTF-8" is returned.
     *
     * @param  nodeArg The Node to serialize
     * @return A String containing the encoding pseudo-attribute of the XMLDecl.
     * @throws Throwable if the DOM implementation does not implement Document.getXmlEncoding()
     */
    protected String getXMLEncoding(Node nodeArg) {
        Document doc = null;

        // Determine the XML Encoding of the document
        if (nodeArg != null) {
            if (nodeArg.getNodeType() == Node.DOCUMENT_NODE) {
                // The Document node is the Node argument
                doc = (Document)nodeArg;
            } else {
                // The Document node is the Node argument's ownerDocument
                doc = nodeArg.getOwnerDocument();
            }

            // Determine the XML Version.
            if (doc != null && doc.getImplementation().hasFeature("Core","3.0")) {
                return doc.getXmlEncoding();
            }
        }
        // The default encoding is UTF-8 except for the writeToString method
        return "UTF-8";
    }

    /**
     * Determines the Input Encoding of the Document Node to serialize.  If the Document Node
     * is not a DOM Level 3 Node, then null is returned.
     *
     * @param  nodeArg The Node to serialize
     * @return A String containing the input encoding.
     */
    protected String getInputEncoding(Node nodeArg)  {
        Document doc = null;

        // Determine the Input Encoding of the document
        if (nodeArg != null) {
            if (nodeArg.getNodeType() == Node.DOCUMENT_NODE) {
                // The Document node is the Node argument
                doc = (Document)nodeArg;
            } else {
                // The Document node is the Node argument's ownerDocument
                doc = nodeArg.getOwnerDocument();
            }

            // Determine the DOM Version.
            if (doc != null && doc.getImplementation().hasFeature("Core","3.0")) {
                return doc.getInputEncoding();
            }
        }
        // The default encoding returned is null
        return null;
    }

    /**
     * This method returns the LSSerializer's error handler.
     *
     * @return Returns the fDOMErrorHandler.
     */
    public DOMErrorHandler getErrorHandler() {
        return fDOMErrorHandler;
    }

}
