XmlFactoryBuilder.java

package tools.jackson.dataformat.xml;

import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;

import tools.jackson.core.ErrorReportConfiguration;
import tools.jackson.core.StreamReadConstraints;
import tools.jackson.core.StreamWriteConstraints;
import tools.jackson.core.base.DecorableTSFactory.DecorableTSFBuilder;
import tools.jackson.dataformat.xml.deser.FromXmlParser;

/**
 * {@link tools.jackson.core.TSFBuilder}
 * implementation for constructing {@link XmlFactory} instances.
 */
public class XmlFactoryBuilder extends DecorableTSFBuilder<XmlFactory, XmlFactoryBuilder>
{
    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Set of {@code FromXmlParser.Feature}s enabled, as bitmask.
     */
    protected int _formatParserFeatures;

    /**
     * Set of {@code ToXmlGenerator.Feature}s enabled, as bitmask.
     */
    protected int _formatGeneratorFeatures;

    /**
     * Stax factory for creating underlying input stream readers;
     * `null` for "use default instance with default settings"
     */
    protected XMLInputFactory _xmlInputFactory;

    /**
     * Stax factory for creating underlying output stream writers;
     * `null` for "use default instance with default settings"
     */
    protected XMLOutputFactory _xmlOutputFactory;

    /**
     * In cases where a start element has both attributes and non-empty textual
     * value, we have to create a bogus property; we will use this as
     * the property name.
     *<p>
     * Name used for pseudo-property used for returning XML Text value (which does
     * not have actual element name to use). Defaults to empty String, but
     * may be changed for inter-operability reasons: JAXB, for example, uses
     * "value" as name.
     */
    protected String _nameForTextElement;

    /**
     * Optional {@link ClassLoader} to use for constructing
     * {@link XMLInputFactory} and {@link XMLOutputFactory} instances if
     * not explicitly specified by caller. If not specified, will
     * default to {@link ClassLoader} that loaded this class.
     */
    protected ClassLoader _classLoaderForStax;

    /**
     * See {@link XmlNameProcessor} and {@link XmlNameProcessors}
     */
    protected XmlNameProcessor _nameProcessor;

    /*
    /**********************************************************************
    /* Life cycle
    /**********************************************************************
     */
    
    protected XmlFactoryBuilder() {
        super(StreamReadConstraints.defaults(),
                StreamWriteConstraints.defaults(),
                ErrorReportConfiguration.defaults(),
                XmlFactory.DEFAULT_XML_READ_FEATURE_FLAGS,
                XmlFactory.DEFAULT_XML_WRITE_FEATURE_FLAGS);
        _classLoaderForStax = null;
        _nameProcessor = XmlNameProcessors.newPassthroughProcessor();
        _nameForTextElement = FromXmlParser.DEFAULT_UNNAMED_TEXT_PROPERTY;
    }

    public XmlFactoryBuilder(XmlFactory base) {
        super(base);
        _xmlInputFactory = base._xmlInputFactory;
        _xmlOutputFactory = base._xmlOutputFactory;
        _nameForTextElement = base._cfgNameForTextElement;
        _nameProcessor = base._nameProcessor;
        _classLoaderForStax = null;
    }

    // // // Accessors

    public String nameForTextElement() { return _nameForTextElement; }

    public XMLInputFactory xmlInputFactory() {
        if (_xmlInputFactory == null) {
            return defaultXmlInputFactory(_classLoaderForStax);
        }
        return _xmlInputFactory;
    }

    protected XMLInputFactory defaultXmlInputFactory() {
        return defaultXmlInputFactory(staxClassLoader());
    }

    protected static XMLInputFactory defaultXmlInputFactory(ClassLoader cl) {
        // 05-Jul-2021, tatu: as per [dataformat-xml#483], consider ClassLoader
        XMLInputFactory xmlIn;
        try {
            xmlIn = XMLInputFactory.newFactory(XMLInputFactory.class.getName(), cl);
        } catch (FactoryConfigurationError | NoSuchMethodError e) {
            // 24-Oct-2022, tatu: as per [dataformat-xml#550] need extra care
            xmlIn = XMLInputFactory.newFactory();
        }
        // as per [dataformat-xml#190], disable external entity expansion by default
        xmlIn.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
        // and ditto wrt [dataformat-xml#211], SUPPORT_DTD
        xmlIn.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
        return xmlIn;
    }

    public XMLOutputFactory xmlOutputFactory() {
        if (_xmlOutputFactory == null) {
            return defaultXmlOutputFactory(_classLoaderForStax);
        }
        return _xmlOutputFactory;
    }

    protected XMLOutputFactory defaultXmlOutputFactory() {
        return defaultXmlOutputFactory(staxClassLoader());
    }

    protected static XMLOutputFactory defaultXmlOutputFactory(ClassLoader cl) {
        // 05-Jul-2021, tatu: as per [dataformat-xml#483], consider ClassLoader
        XMLOutputFactory xmlOut;
        try {
            xmlOut = XMLOutputFactory.newFactory(XMLOutputFactory.class.getName(), cl);
        } catch (FactoryConfigurationError | NoSuchMethodError e) {
            // 24-Oct-2022, tatu: as per [dataformat-xml#550] need extra care
            xmlOut = XMLOutputFactory.newFactory();
        }
        // [dataformat-xml#326]: Better ensure namespaces get built properly:
        xmlOut.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, Boolean.TRUE);
        return xmlOut;
    }

    protected ClassLoader staxClassLoader() {
        return (_classLoaderForStax == null) ?
                getClass().getClassLoader() : _classLoaderForStax;
    }

    public XmlNameProcessor xmlNameProcessor() {
        return _nameProcessor;
    }

    // // // Parser features

    public XmlFactoryBuilder enable(XmlReadFeature f) {
        _formatParserFeatures |= f.getMask();
        return _this();
    }

    public XmlFactoryBuilder enable(XmlReadFeature first, XmlReadFeature... other) {
        _formatParserFeatures |= first.getMask();
        for (XmlReadFeature f : other) {
            _formatParserFeatures |= f.getMask();
        }
        return _this();
    }

    public XmlFactoryBuilder disable(XmlReadFeature f) {
        _formatParserFeatures &= ~f.getMask();
        return _this();
    }

    public XmlFactoryBuilder disable(XmlReadFeature first, XmlReadFeature... other) {
        _formatParserFeatures &= ~first.getMask();
        for (XmlReadFeature f : other) {
            _formatParserFeatures &= ~f.getMask();
        }
        return _this();
    }

    public XmlFactoryBuilder configure(XmlReadFeature f, boolean state) {
        return state ? enable(f) : disable(f);
    }

    // // // Generator features

    public XmlFactoryBuilder enable(XmlWriteFeature f) {
        _formatGeneratorFeatures |= f.getMask();
        return _this();
    }

    public XmlFactoryBuilder enable(XmlWriteFeature first, XmlWriteFeature... other) {
        _formatGeneratorFeatures |= first.getMask();
        for (XmlWriteFeature f : other) {
            _formatGeneratorFeatures |= f.getMask();
        }
        return _this();
    }

    public XmlFactoryBuilder disable(XmlWriteFeature f) {
        _formatGeneratorFeatures &= ~f.getMask();
        return _this();
    }
    
    public XmlFactoryBuilder disable(XmlWriteFeature first, XmlWriteFeature... other) {
        _formatGeneratorFeatures &= ~first.getMask();
        for (XmlWriteFeature f : other) {
            _formatGeneratorFeatures &= ~f.getMask();
        }
        return _this();
    }

    public XmlFactoryBuilder configure(XmlWriteFeature f, boolean state) {
        return state ? enable(f) : disable(f);
    }

    // // // Other config

    public XmlFactoryBuilder nameForTextElement(String name) {
        _nameForTextElement = name;
        return _this();
    }

    public XmlFactoryBuilder xmlInputFactory(XMLInputFactory xmlIn) {
        _xmlInputFactory = xmlIn;
        return _this();
    }

    public XmlFactoryBuilder xmlOutputFactory(XMLOutputFactory xmlOut) {
        _xmlOutputFactory = xmlOut;
        return _this();
    }

    /**
     * Method that can be used to specific {@link ClassLoader} for creating
     * {@link XMLInputFactory} and {@link XMLOutputFactory} instances if
     * those are not explicitly defined by caller: passed to respective
     * {@code newFactory()} methods.
     *<br>
     * NOTE: recommended approach is to explicitly pass {@link XMLInputFactory}
     * and {@link XMLOutputFactory} methods instead of relying on JDK SPI
     * mechanism.
     */
    public XmlFactoryBuilder staxClassLoader(ClassLoader cl) {
        _classLoaderForStax = cl;
        return _this();
    }

    public XmlFactoryBuilder xmlNameProcessor(XmlNameProcessor nameProcessor) {
        _nameProcessor = nameProcessor;
        return _this();
    }

    // // // Actual construction

    @Override
    public XmlFactory build() {
        return new XmlFactory(this);
    }
}