/*
 * Copyright (c) 2017, 2021, 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.Properties;
import javax.xml.transform.OutputKeys;
import jdk.xml.internal.SecuritySupport;

/**
 * This class is a factory to generate a set of default properties
 * of key/value pairs that are used to create a serializer through the
 * factory {@link SerializerFactory SerilizerFactory}.
 * The properties generated by this factory
 * may be modified to non-default values before the SerializerFactory is used to
 * create a Serializer.
 * <p>
 * The given output types supported are "xml", "text", and "html".
 * These type strings can be obtained from the
 * {@link Method Method} class in this package.
 * <p>
 * Other constants defined in this class are the non-standard property keys
 * that can be used to set non-standard property values on a java.util.Properties object
 * that is used to create or configure a serializer. Here are the non-standard keys:
 * <ul>
 * <li> <b>S_KEY_INDENT_AMOUNT </b> -
 * The non-standard property key to use to set the indentation amount.
 * The "indent" key needs to have a value of "yes", and this
 * properties value is a the number of whitespaces to indent by per
 * indentation level.
 *
 * <li> <b>S_KEY_CONTENT_HANDLER </b> -
 * This non-standard property key is used to set the name of the fully qualified
 * Java class that implements the ContentHandler interface.
 * The output of the serializer will be SAX events sent to this an
 * object of this class.
 *
 * <li> <b>S_KEY_ENTITIES </b> -
 * This non-standard property key is used to specify the name of the property file
 * that specifies character to entity reference mappings. A line in such a
 * file is has the name of the entity and the numeric (base 10) value
 * of the corresponding character, like this one: <br> quot=34 <br>
 *
 * <li> <b>S_USE_URL_ESCAPING </b> -
 * This non-standard property key is used to set a value of "yes" if the href values
 * for HTML serialization should use %xx escaping.
 *
 * <li> <b>S_OMIT_META_TAG </b> -
 * This non-standard property key is used to set a value of "yes" if the META tag
 * should be omitted where it would otherwise be supplied.
 * </ul>
 *
 * @see SerializerFactory
 * @see Method
 * @see Serializer
 * @LastModified: May 2021
 */
public final class OutputPropertiesFactory
{
    /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace
     *(http://xml.apache.org/xalan) predefined to signify Xalan's
     * built-in XSLT Extensions. When used in stylesheets, this is often
     * bound to the "xalan:" prefix.
     */
    private static final String
      S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";

    /**
     * The old built-in extension url. It is still supported for
     * backward compatibility.
     */
    private static final String
      S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt";

    //************************************************************
    //*  PUBLIC CONSTANTS
    //************************************************************
    /**
     * This is not a public API.
     * This is the built-in extensions namespace,
     * reexpressed in {namespaceURI} syntax
     * suitable for prepending to a localname to produce a "universal
     * name".
     */
    public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL =
        "{" + S_BUILTIN_EXTENSIONS_URL + "}";

    // Some special Xalan keys.

    /**
     * The non-standard property key to use to set the
     * number of whitepaces to indent by, per indentation level,
     * if indent="yes".
     */
    public static final String S_KEY_INDENT_AMOUNT =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "indent-amount";

    /**
     * The non-standard property key to use to set the
     * number of whitepaces to indent by, per indentation level,
     * if indent="yes".
     */
    public static final String S_KEY_LINE_SEPARATOR =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "line-separator";

    /** This non-standard property key is used to set the name of the fully qualified
     * Java class that implements the ContentHandler interface.
     * Fully qualified name of class with a default constructor that
     *  implements the ContentHandler interface, where the result tree events
     *  will be sent to.
     */

    public static final String S_KEY_CONTENT_HANDLER =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "content-handler";

    /**
     * This non-standard property key is used to specify the name of the property file
     * that specifies character to entity reference mappings.
     */
    public static final String S_KEY_ENTITIES =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "entities";

    /**
     * This non-standard property key is used to set a value of "yes" if the href
     * values for HTML serialization should use %xx escaping.
     */
    public static final String S_USE_URL_ESCAPING =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "use-url-escaping";

    /**
     * This non-standard property key is used to set a value of "yes" if the META
     * tag should be omitted where it would otherwise be supplied.
     */
    public static final String S_OMIT_META_TAG =
        S_BUILTIN_EXTENSIONS_UNIVERSAL + "omit-meta-tag";

    /**
     * The old built-in extension namespace, this is not a public API.
     */
    public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL =
        "{" + S_BUILTIN_OLD_EXTENSIONS_URL + "}";

    /**
     * This is not a public API, it is only public because it is used
     * by outside of this package,
     * it is the length of the old built-in extension namespace.
     */
    public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN =
        S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();


    //************************************************************
    //*  PRIVATE CONSTANTS
    //************************************************************

    /*
     * XSLT properties do not need namespace qualification.
     *
     * Xalan-specific output properties can be overridden in the stylesheet
     * assigning a xalan namespace.  For example:
     * <xsl:stylesheet version="1.0"
     *          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     *          xmlns:xalan="http://xml.apache.org/xalan">
     *  <xsl:output method="html" encoding="UTF-8"
     *              xalan:content-handler="MyContentHandler"/>
     *  ...
     */
    private static final String[] PROP_XML = {
        "method",
        "version",
        "encoding",
        "indent",
        "omit-xml-declaration",
        "standalone",
        "media-type",
        "{http://xml.apache.org/xalan}indent-amount",
        "{http://xml.apache.org/xalan}content-handler",
        "{http://xml.apache.org/xalan}entities"
    };

    private static final String[] PROP_XML_VALUE = {
        "xml",
        "1.0",
        "UTF-8",
        "no",
        "no",
        "no",
        "text/xml",
        "0",
        "com.sun.org.apache.xml.internal.serializer.ToXMLStream",
        "com/sun/org/apache/xml/internal/serializer/XMLEntities"
    };

    private static final String[] PROP_HTML = {
        "method",
        "indent",
        "media-type",
        "version",
        "{http://xml.apache.org/xalan}indent-amount",
        "{http://xml.apache.org/xalan}content-handler",
        "{http://xml.apache.org/xalan}entities",
        "{http://xml.apache.org/xalan}use-url-escaping",
        "{http://xml.apache.org/xalan}omit-meta-tag"
    };

    private static final String[] PROP_HTML_VALUE = {
        "html",
        "yes",
        "text/html",
        "4.0",
        "4",
        "com.sun.org.apache.xml.internal.serializer.ToHTMLStream",
        "com/sun/org/apache/xml/internal/serializer/HTMLEntities",
        "yes",
        "no"
    };

    private static final String[] PROP_TEXT = {
        "method",
        "media-type",
        "{http://xml.apache.org/xalan}content-handler"
    };

    private static final String[] PROP_TEXT_VALUE = {
        "text",
        "text/plain",
        "com.sun.org.apache.xml.internal.serializer.ToTextStream"
    };

    private static final String[] PROP_UNKNOWN = {
        "method",
        "version",
        "encoding",
        "indent",
        "omit-xml-declaration",
        "standalone",
        "media-type",
        "{http://xml.apache.org/xalan}indent-amount",
        "{http://xml.apache.org/xalan}content-handler"
    };

    private static final String[] PROP_UNKNOWN_VALUE = {
        "xml",
        "1.0",
        "UTF-8",
        "no",
        "no",
        "no",
        "text/xml",
        "0",
        "com.sun.org.apache.xml.internal.serializer.ToUnknownStream",
    };

    //************************************************************
    //*  PRIVATE STATIC FIELDS
    //************************************************************

    /** The default properties for all other than html and text. */
    private static Properties m_xml_properties = null;

    /** The default properties when method="html". */
    private static Properties m_html_properties = null;

    /** The default properties when method="text". */
    private static Properties m_text_properties = null;

    /** The properties when method="" for the "unknown" wrapper */
    private static Properties m_unknown_properties = null;

    /**
     * Returns a Properties based on the specified method. The default is xml.
     *
     * @param   method non-null reference to method name.
     *
     * @return Properties object that holds the defaults for the given method.
     */
    static public final Properties getDefaultMethodProperties(String method)
    {
        Properties defaultProperties = null;

        if (null == m_xml_properties) {
            m_xml_properties = initProperties(PROP_XML, PROP_XML_VALUE, null);
        }


        switch (method) {
            case Method.XML:
                defaultProperties = m_xml_properties;
                break;
            case Method.HTML:
                if (null == m_html_properties) {
                    m_html_properties = initProperties(
                            PROP_HTML, PROP_HTML_VALUE, m_xml_properties);
                }
                defaultProperties = m_html_properties;
                break;
            case Method.TEXT:
                if (null == m_text_properties) {
                    m_text_properties = initProperties(
                            PROP_TEXT, PROP_TEXT_VALUE, m_xml_properties);

                    if (null == m_text_properties.getProperty(OutputKeys.ENCODING))
                    {
                        String mimeEncoding = Encodings.getMimeEncoding(null);
                        m_text_properties.put(OutputKeys.ENCODING, mimeEncoding);
                    }
                }
                defaultProperties = m_text_properties;
                break;
            case com.sun.org.apache.xml.internal.serializer.Method.UNKNOWN:
                if (null == m_unknown_properties) {
                    m_unknown_properties = initProperties(
                            PROP_UNKNOWN, PROP_UNKNOWN_VALUE, m_xml_properties);
                }
                defaultProperties = m_unknown_properties;
                break;
            default:
                defaultProperties = m_xml_properties;
                break;
        }

        // wrap these cached defaultProperties in a new Property object just so
        // that the caller of this method can't modify the default values
        return new Properties(defaultProperties);
    }

    /**
     * Initiates the properties
     *
     * @param keys an array of keys
     * @param values values corresponding to the keys
     * @param defaults Default properties, which may be null.
     */
    static private Properties initProperties(String[] keys, String[] values, Properties defaults)
    {
        Properties props = new Properties(defaults);

        for (int i = 0; i < keys.length; i++) {
            // check System Property. This is kept as is for binary compatibility
            String sys = SecuritySupport.getSystemProperty(keys[i]);
            props.put(keys[i], (sys == null) ? values[i] : sys);
        }

        return props;
    }
}
