package org.orbeon.oxf.xml.xerces;

import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.xml.XMLParsing;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * Boasts a couple of improvements over the 'stock' xerces parser factory.
 *
 * o Doesn't create a new parser every time one calls setFeature or getFeature.  Stock one
 *   has to do this because valid feature set is encapsulated in the parser code.
 *
 * o Creates a XercesJAXPSAXParser instead of SaxParserImpl. See XercesJAXPSAXParser for
 *   why this is an improvement.
 *
 * o The improvements cut the time it takes to a SAX parser via JAXP in
 *   half and reduce the amount of garbage created when accessing '/' in
 *   the examples app from 9000000 bytes to 8402880 bytes.
 */
public class XercesSAXParserFactoryImpl extends SAXParserFactory {

    private final Map<String, Boolean> features;
    private final Set<String> recognizedFeatures;
    private final XMLParsing.ParserConfiguration parserConfiguration;

    public XercesSAXParserFactoryImpl() {
        this(XMLParsing.ParserConfiguration.PLAIN);
    }

    public XercesSAXParserFactoryImpl(XMLParsing.ParserConfiguration parserConfiguration) {
        this.parserConfiguration = parserConfiguration;

        // NOTE: Creating a configuration can be expensive, so callers should create factories sparingly
        final OrbeonParserConfiguration configuration = XercesSAXParser.makeConfig(parserConfiguration);

        this.recognizedFeatures = Collections.unmodifiableSet(configuration.getRecognizedFeatures());
        this.features = configuration.getFeatures();

        setNamespaceAware(true); // this is needed by some tools in addition to the feature
        setValidating(parserConfiguration.validating);
    }

    public boolean getFeature(final String key) throws SAXNotRecognizedException {
        if (!recognizedFeatures.contains(key)) throw new SAXNotRecognizedException(key);
        return features.get(key) == Boolean.TRUE;
    }

    public void setFeature(final String key, final boolean val) throws SAXNotRecognizedException {
        if (!recognizedFeatures.contains(key)) throw new SAXNotRecognizedException(key);
        features.put(key, val ? Boolean.TRUE : Boolean.FALSE);
    }

    public SAXParser newSAXParser() {
        try {
            final SAXParser result = new XercesJAXPSAXParser(this, features, parserConfiguration);
            // Set security manager before returning the parser
            result.setProperty("http://apache.org/xml/properties/security-manager", new orbeon.apache.xerces.util.SecurityManager());
            return result;
        } catch (final SAXException se) {
            // Translate to ParserConfigurationException
            throw new OXFException(se); // so we see a decent stack trace!
        }
    }
}