/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed 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 org.apache.xmlbeans;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Corresponds to the Saver and XmlOptions.
 * <p>
 * This class is used to set up a map containing characters to be escaped.
 * Characters can be escaped as hex, decimal or as a predefined entity (this
 * latter option applies only to the 5 characters defined as predefined entities
 * in the XML Spec).
 * <p>
 * For example:
 * <pre>{@code
 *      XmlOptionCharEscapeMap escapes = new XmlOptionCharEscapeMap();
 *      escapes.addMapping('A', XmlOptionCharEscapeMap.HEXADECIMAL);
 *      escapes.addMapping('B', XmlOptionCharEscapeMap.DECIMAL);
 *      escapes.addMapping('>', XmlOptionCharEscapeMap.PREDEF_ENTITY);
 *
 *      XmlOptions opts = new XmlOptions();
 *      opts.setSaveSubstituteCharacters(escapes);
 *      System.out.println(myXml.xmlText(opts));
 *
 *      will result in:
 *      A being printed as &#x41;
 *      B being printed as &#66;
 *      > being printed as &gt;
 *
 * }</pre>
 */
public class XmlOptionCharEscapeMap {
    public static final int PREDEF_ENTITY = 0;
    public static final int DECIMAL = 1;
    public static final int HEXADECIMAL = 2;

    // map of Character to String which will represent it in the output document
    private final Map<Character, String> _charMap;

    // internal HashMap just for predefined entities
    private static final Map<Character, String> _predefEntities;

    static {
        final Map<Character, String> predefEntities = new HashMap<>();
        predefEntities.put('<', "&lt;");
        predefEntities.put('>', "&gt;");
        predefEntities.put('&', "&amp;");
        predefEntities.put('\'', "&apos;");
        predefEntities.put('"', "&quot;");
        _predefEntities = Collections.unmodifiableMap(predefEntities);
    }

    /**
     * Construct a new XmlOptionCharEncoder.
     */
    public XmlOptionCharEscapeMap() {
        _charMap = new HashMap<>();
    }

    /**
     * @return whether a character encoding exists for this character
     */
    public boolean containsChar(char ch) {
        return _charMap.containsKey(ch);
    }

    /**
     * set up this character to be escaped in output documents
     * according to the given mode
     */
    public void addMapping(char ch, int mode) throws XmlException {
        Character theChar = ch;
        switch (mode) {
            case PREDEF_ENTITY:
                String replString = _predefEntities.get(theChar);
                if (replString == null) {
                    throw new XmlException("XmlOptionCharEscapeMap.addMapping(): " +
                                           "the PREDEF_ENTITY mode can only be used for the following " +
                                           "characters: <, >, &, \" and '");
                }
                _charMap.put(theChar, replString);
                break;

            case DECIMAL:
                _charMap.put(theChar, "&#" + (int) ch + ";");
                break;

            case HEXADECIMAL:
                String hexCharPoint = Integer.toHexString(ch);
                _charMap.put(theChar, "&#x" + hexCharPoint + ";");
                break;

            default:
                throw new XmlException("XmlOptionCharEscapeMap.addMapping(): " +
                                       "mode must be PREDEF_ENTITY, DECIMAL or HEXADECIMAL");
        }
    }

    /**
     * set up this contiguous set of characters to be escaped in
     * output documents according to the given mode
     */
    public void addMappings(char ch1, char ch2, int mode) throws XmlException {
        if (ch1 > ch2) {
            throw new XmlException("XmlOptionCharEscapeMap.addMappings(): " +
                                   "ch1 must be <= ch2");
        }

        for (char c = ch1; c <= ch2; c++) {
            addMapping(c, mode);
        }
    }

    /**
     * returns the escaped String for the character
     */
    public String getEscapedString(char ch) {
        return _charMap.get(ch);
    }
}
