TomlFactory.java

package tools.jackson.dataformat.toml;

import java.io.*;

import tools.jackson.core.*;
import tools.jackson.core.TokenStreamLocation;
import tools.jackson.core.base.TextualTSFactory;
import tools.jackson.core.io.GuardedOutputStream;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.io.UTF8Writer;

import tools.jackson.databind.node.ObjectNode;
import tools.jackson.databind.node.TreeTraversingParser;

public final class TomlFactory extends TextualTSFactory
{
    private final static long serialVersionUID = 1L;

    public final static String FORMAT_NAME_TOML = "toml";

    /**
     * Bitfield (set of flags) of all generator features that are enabled
     * by default.
     */
    final static int DEFAULT_TOML_PARSER_FEATURE_FLAGS = TomlReadFeature.collectDefaults();

    /**
     * Bitfield (set of flags) of all generator features that are enabled
     * by default.
     */
    final static int DEFAULT_TOML_GENERATOR_FEATURE_FLAGS = TomlWriteFeature.collectDefaults();

    /*
    /**********************************************************************
    /* Factory construction, configuration
    /**********************************************************************
     */

    public TomlFactory() {
        super(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(),
                ErrorReportConfiguration.defaults(),
                DEFAULT_TOML_PARSER_FEATURE_FLAGS, DEFAULT_TOML_GENERATOR_FEATURE_FLAGS);
    }

    TomlFactory(TomlFactory src) {
        super(src);
    }

    /**
     * Constructors used by {@link TomlFactoryBuilder} for instantiation.
     *
     * @since 3.0
     */
    TomlFactory(TomlFactoryBuilder b) {
        super(b);
    }

    @Override
    public TomlFactoryBuilder rebuild() {
        return new TomlFactoryBuilder(this);
    }

    /**
     * Main factory method to use for constructing {@link TomlFactory} instances with
     * different configuration.
     */
    public static TomlFactoryBuilder builder() {
        return new TomlFactoryBuilder();
    }

    @Override
    public TomlFactory copy() {
        return new TomlFactory(this);
    }

    /**
     * Instances are immutable so just return `this`
     */
    @Override
    public TokenStreamFactory snapshot() {
        return this;
    }

    /*
    /**********************************************************************
    /* Serializable overrides
    /**********************************************************************
     */

    /**
     * Method that we need to override to actually make restoration go
     * through constructors etc.
     */
    protected Object readResolve() {
        return new TomlFactory(this);
    }

    /*
    /**********************************************************************
    /* Capability introspection
    /**********************************************************************
     */

    @Override
    public Version version() {
        return PackageVersion.VERSION;
    }

    @Override
    public boolean requiresPropertyOrdering() {
        return false;
    }

    @Override
    public boolean canUseCharArrays() {
        return false;
    }

    @Override
    public boolean canParseAsync() {
        return false;
    }

    /*
    /**********************************************************************
    /* Format support
    /**********************************************************************
     */

    @Override
    public String getFormatName() {
        return FORMAT_NAME_TOML;
    }

    @Override
    public boolean canUseSchema(FormatSchema schema) {
        return false;
    }

    @Override
    public Class<? extends FormatFeature> getFormatReadFeatureType() {
        return TomlReadFeature.class;
    }

    @Override
    public Class<? extends FormatFeature> getFormatWriteFeatureType() {
        return TomlWriteFeature.class;
    }

    @Override
    public int getFormatReadFeatures() {
        return _formatReadFeatures;
    }

    @Override
    public int getFormatWriteFeatures() {
        return _formatWriteFeatures;
    }

    public boolean isEnabled(TomlReadFeature f) {
        return (_formatReadFeatures & f.getMask()) != 0;
    }

    public boolean isEnabled(TomlWriteFeature f) {
        return (_formatWriteFeatures & f.getMask()) != 0;
    }

    /*
    /**********************************************************************
    /* Overridden internal factory methods, parser
    /**********************************************************************
     */

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, InputStream in) throws JacksonException {
        // "A TOML file must be a valid UTF-8 encoded Unicode document."
        // Reader is constructed (and hence owned) by us, so it must always be closed to
        // have its read buffer recycled. A source Jackson opened (File/Path) is closed by
        // the base factory when construction fails [dataformats-text#720], so the reader
        // must not close it too; we close that source only after a successful parse.
        // A caller-owned stream is closed with the reader when auto-close is enabled,
        // because the base factory does not close caller sources.
        final boolean managed = ctxt.isResourceManaged();
        final boolean closeStreamWithReader = !managed
                && StreamReadFeature.AUTO_CLOSE_SOURCE.enabledIn(
                        readCtxt.getStreamReadFeatures(_streamReadFeatures));
        JsonParser parser = _createParser(readCtxt, ctxt,
                UTF8Reader.construct(ctxt, in, closeStreamWithReader), true);
        if (managed) {
            try {
                in.close();
            } catch (IOException e) {
                throw _wrapIOFailure(e);
            }
        }
        return parser;
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, Reader r) throws JacksonException {
        // Reader is the caller's, so only close it if auto-closing is enabled
        return _createParser(readCtxt, ctxt, r, _autoCloseSource(readCtxt, ctxt));
    }

    private JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, Reader r,
            boolean closeReader) throws JacksonException {
        try {
            ObjectNode node = parse(readCtxt, ctxt, r, closeReader);
            return new TreeTraversingParser(node, readCtxt, ctxt.streamReadConstraints());
        } finally {
            ctxt.close();
        }
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, byte[] data, int offset, int len) throws JacksonException {
        return _createParser(readCtxt, ctxt, UTF8Reader.construct(data, offset, len));
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, char[] data, int offset, int len, boolean recyclable) throws JacksonException {
        return _createParser(readCtxt, ctxt, new CharArrayReader(data, offset, len));
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ctxt, DataInput input) throws JacksonException {
        return _unsupported();
    }

    /*
    /**********************************************************************
    /* Overridden internal factory methods, generator
    /**********************************************************************
     */

    @Override
    protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt, IOContext ioCtxt, Writer out) throws JacksonException {
        return new TomlGenerator(writeCtxt, ioCtxt,
                writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
                writeCtxt.getFormatWriteFeatures(_formatWriteFeatures),
                out);
    }

    @Override
    protected JsonGenerator _createUTF8Generator(ObjectWriteContext writeCtxt, IOContext ioCtxt, OutputStream out) throws JacksonException {
        // [dataformats-text#735]: Writer is constructed (and hence owned) by us, so the
        // generator must always flush and close it -- that hands its pending content over
        // and recycles its buffer. It is shielded from the caller's stream, which the
        // generator itself flushes or closes as per `FLUSH_PASSED_TO_STREAM` and
        // `AUTO_CLOSE_TARGET` (as enabled at that point).
        return new TomlGenerator(writeCtxt, ioCtxt,
                writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
                writeCtxt.getFormatWriteFeatures(_formatWriteFeatures),
                new UTF8Writer(ioCtxt, new GuardedOutputStream(out)), out);
    }

    @Override
    protected Writer _createWriter(IOContext ioCtxt, OutputStream out, JsonEncoding enc) throws JacksonException {
        // "A TOML file must be a valid UTF-8 encoded Unicode document."
        return new UTF8Writer(ioCtxt, out);
    }

    /*
    /**********************************************************************
    /* Low-level methods for reading/writing TOML
    /**********************************************************************
     */

    private boolean _autoCloseSource(ObjectReadContext readCtxt, IOContext ctxt) {
        return ctxt.isResourceManaged()
                || StreamReadFeature.AUTO_CLOSE_SOURCE.enabledIn(
                        readCtxt.getStreamReadFeatures(_streamReadFeatures));
    }

    private ObjectNode parse(ObjectReadContext readCtxt, IOContext ctxt, Reader r0,
            boolean closeReader) {
        int readFeatures = readCtxt.getFormatReadFeatures(DEFAULT_TOML_PARSER_FEATURE_FLAGS);
        try {
            if (closeReader) {
                try (Reader r = r0) {
                    return TomlParser.parse(this, ctxt, readFeatures, r);
                }
            } else {
                return TomlParser.parse(this, ctxt, readFeatures, r0);
            }
        } catch (CharConversionException e) {
            TokenStreamLocation location;
            if (e instanceof UTF8Reader.InvalidUTF8Exception utf8Ex) {
                location = new TokenStreamLocation(ctxt.contentReference(),
                        utf8Ex.byteOffset, utf8Ex.charOffset, -1, -1);
            } else {
                location = new TokenStreamLocation(ctxt.contentReference(), -1, -1, -1, -1);
            }
            throw new TomlStreamReadException(null, e.getMessage(), location, e);
        } catch (IOException e) {
            throw _wrapIOFailure(e);
        }
    }
}