JsonFactory.java

/* Jackson JSON-processor.
 *
 * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi
 */
package tools.jackson.core.json;

import java.io.*;
import java.util.List;
import java.util.Locale;

import tools.jackson.core.*;
import tools.jackson.core.base.TextualTSFactory;
import tools.jackson.core.io.*;
import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser;
import tools.jackson.core.json.async.NonBlockingByteBufferJsonParser;
import tools.jackson.core.sym.BinaryNameMatcher;
import tools.jackson.core.sym.ByteQuadsCanonicalizer;
import tools.jackson.core.sym.CharsToNameCanonicalizer;
import tools.jackson.core.sym.PropertyNameMatcher;
import tools.jackson.core.util.DefaultPrettyPrinter;
import tools.jackson.core.util.Named;

/**
 * JSON-backed {@link TokenStreamFactory} implementation that will create
 * token readers ("parsers") and writers ("generators") for handling
 * JSON-encoded content.
 *<p>
 * Note that this class used to reside at main {@code tools.jackson.core}
 * in 2.x, but moved here to denote its changed role as implementation,
 * not base class for factories.
 */
public class JsonFactory
    extends TextualTSFactory
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1;

    /*
    /**********************************************************************
    /* Constants
    /**********************************************************************
     */

    /**
     * Name used to identify JSON format
     * (and returned by {@link #getFormatName()}
     */
    public final static String FORMAT_NAME_JSON = "JSON";

    /**
     * Bitfield (set of flags) of all parser features that are enabled
     * by default.
     */
    final static int DEFAULT_JSON_PARSER_FEATURE_FLAGS = JsonReadFeature.collectDefaults();

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

    public final static SerializableString DEFAULT_ROOT_VALUE_SEPARATOR = DefaultPrettyPrinter.DEFAULT_ROOT_VALUE_SEPARATOR;

    public final static char DEFAULT_QUOTE_CHAR = '"';

    /*
    /**********************************************************************
    /* Configuration
    /**********************************************************************
     */

    /**
     * Definition of custom character escapes to use for generators created
     * by this factory, if any. If null, standard data format specific
     * escapes are used.
     */
    protected final CharacterEscapes _characterEscapes;

    /**
     * Separator used between root-level values, if any; null indicates
     * "do not add separator".
     * Default separator is a single space character.
     */
    protected final SerializableString _rootValueSeparator;

    /**
     * Optional threshold used for automatically escaping character above certain character
     * code value: either {@code 0} to indicate that no threshold is specified, or value
     * at or above 127 to indicate last character code that is NOT automatically escaped
     * (but depends on other configuration rules for checking).
     */
    protected final int _maximumNonEscapedChar;

    /**
     * Character used for quoting property names (if property name quoting has not
     * been disabled with {@link JsonWriteFeature#QUOTE_PROPERTY_NAMES})
     * and JSON String values.
     */
    protected final char _quoteChar;

    /*
    /**********************************************************************
    /* Symbol table management
    /**********************************************************************
     */

    /**
     * Each factory comes equipped with a shared root symbol table.
     * It should not be linked back to the original blueprint, to
     * avoid contents from leaking between factories.
     */
    protected final transient CharsToNameCanonicalizer _rootCharSymbols;

    /**
     * Alternative to the basic symbol table, some stream-based
     * parsers use different name canonicalization method.
     */
    protected final transient ByteQuadsCanonicalizer _byteSymbolCanonicalizer = ByteQuadsCanonicalizer.createRoot();

    /*
    /**********************************************************************
    /* Construction
    /**********************************************************************
     */

    /**
     * Default constructor used to create factory instances.
     * Creation of a factory instance is a light-weight operation,
     * but it is still a good idea to reuse limited number of
     * factory instances (and quite often just a single instance):
     * factories are used as context for storing some reused
     * processing objects (such as symbol tables parsers use)
     * and this reuse only works within context of a single
     * factory instance.
     */
    public JsonFactory() {
        super(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(),
                ErrorReportConfiguration.defaults(),
                DEFAULT_JSON_PARSER_FEATURE_FLAGS, DEFAULT_JSON_GENERATOR_FEATURE_FLAGS);
        _rootValueSeparator = DEFAULT_ROOT_VALUE_SEPARATOR;
        _characterEscapes = null;
        _maximumNonEscapedChar = 0; // disabled
        _quoteChar = DEFAULT_QUOTE_CHAR;
        _rootCharSymbols = CharsToNameCanonicalizer.createRoot(this);
    }

    /**
     * Copy constructor.
     *
     * @param src Original factory to copy configuration from
     */
    protected JsonFactory(JsonFactory src)
    {
        super(src);
        _rootValueSeparator = src._rootValueSeparator;
        _characterEscapes = src._characterEscapes;
        _maximumNonEscapedChar = src._maximumNonEscapedChar;
        _quoteChar = src._quoteChar;
        _rootCharSymbols = CharsToNameCanonicalizer.createRoot(this);
    }

    /**
     * Constructors used by {@link JsonFactoryBuilder} for instantiation.
     *
     * @param b Builder that has configuration to use
     *
     * @since 3.0
     */
    protected JsonFactory(JsonFactoryBuilder b)
    {
        super(b);
        _rootValueSeparator = b.rootValueSeparator();
        _characterEscapes = b.characterEscapes();
        _maximumNonEscapedChar = b.highestNonEscapedChar();
        _quoteChar = b.quoteChar();
        _rootCharSymbols = CharsToNameCanonicalizer.createRoot(this);
    }

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

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

    /**
     * Factory method to use for constructing {@link JsonFactory} instances with
     * different configuration. The builder returned uses default settings more closely
     * matching the default configs used in Jackson 2.x versions.
     * <p>
     *     This method is still a work in progress and may not yet fully replicate the
     *     default settings of Jackson 2.x.
     * </p>
     *
     * @return Builder instance to use
     */
    public static JsonFactoryBuilder builderWithJackson2Defaults() {
        return builder().configureForJackson2();
    }

    /**
     * Method for constructing a new {@link JsonFactory} that has
     * the same settings as this instance, but is otherwise
     * independent (i.e. nothing is actually shared, symbol tables
     * are separate).
     *
     * @return Copy of this factory instance
     */
    @Override
    public JsonFactory copy() {
        return new JsonFactory(this);
    }

    @Override
    public TokenStreamFactory snapshot() {
        return this;
    }

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

    /**
     * Method that we need to override to actually make restoration go
     * through constructors etc; called by JDK serialization system.
     *
     * @return A properly initialized copy of this factory instance
     */
    protected Object readResolve() {
        return new JsonFactory(this);
    }

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

    /**
     * Accessor for getting version of the core package, given a parser instance.
     * Left for sub-classes to implement.
     *
     * @return Version of this generator (derived from version declared for
     *   {@code jackson-core} jar that contains the class
     */
    @Override
    public Version version() {
        return PackageVersion.VERSION;
    }

    @Override
    public boolean canParseAsync() {
        // Jackson 2.9 and later do support async parsing for JSON
        return true;
    }

    /**
     * Checked whether specified parser feature is enabled.
     *
     * @param f Feature to check
     *
     * @return {@code True} if feature is enabled; {@code false} otherwise
     */
    public final boolean isEnabled(JsonReadFeature f) {
        return (_formatReadFeatures & f.getMask()) != 0;
    }

    /**
     * Check whether specified generator feature is enabled.
     *
     * @param f Feature to check
     *
     * @return {@code True} if feature is enabled; {@code false} otherwise
     */
    public final boolean isEnabled(JsonWriteFeature f) {
        return (_formatWriteFeatures & f.getMask()) != 0;
    }

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

    /**
     * Method that can be used to quickly check whether given schema
     * is something that parsers and/or generators constructed by this
     * factory could use. Note that this means possible use, at the level
     * of data format (i.e. schema is for same data format as parsers and
     * generators this factory constructs); individual schema instances
     * may have further usage restrictions.
     */
    @Override
    public boolean canUseSchema(FormatSchema schema) {
        return false; // no FormatSchema for json
    }

    /**
     * Method that returns short textual id identifying format
     * this factory supports.
     *<p>
     * Note: sub-classes should override this method; default
     * implementation will return null for all sub-classes
     */
    @Override
    public String getFormatName() {
        return FORMAT_NAME_JSON;
    }

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

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

    /*
    /**********************************************************************
    /* Configuration accessors
    /**********************************************************************
     */

    /**
     * Method for accessing custom escapes factory uses for {@link JsonGenerator}s
     * it creates.
     *
     * @return CharacterEscapes configured to be used by parser instances
     */
    public CharacterEscapes getCharacterEscapes() { return _characterEscapes; }

    public String getRootValueSeparator() {
        return (_rootValueSeparator == null) ? null : _rootValueSeparator.getValue();
    }

    /*
    /**********************************************************************
    /* Parser factories, non-blocking (async) sources
    /**********************************************************************
     */

    @Override
    public JsonParser createNonBlockingByteArrayParser(ObjectReadContext readCtxt)
    {
        IOContext ioCtxt = _createNonBlockingContext(null);
        ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
        return new NonBlockingByteArrayJsonParser(readCtxt, ioCtxt,
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                can);
    }

    @Override
    public JsonParser createNonBlockingByteBufferParser(ObjectReadContext readCtxt)
    {
        IOContext ioCtxt = _createNonBlockingContext(null);
        ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
        return new NonBlockingByteBufferJsonParser(readCtxt, ioCtxt,
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                can);
    }

    protected IOContext _createNonBlockingContext(Object srcRef) {
        return new IOContext(_streamReadConstraints, _streamWriteConstraints,
                _errorReportConfiguration,
                _getBufferRecycler(),
                ContentReference.rawReference(srcRef), false, JsonEncoding.UTF8);
    }

    /*
    /**********************************************************************
    /* Factory methods used by factory for creating parser instances
    /**********************************************************************
     */

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            InputStream in) throws JacksonException
    {
        try {
            return new ByteSourceJsonBootstrapper(ioCtxt, in)
                    .constructParser(readCtxt,
                        readCtxt.getStreamReadFeatures(_streamReadFeatures),
                        readCtxt.getFormatReadFeatures(_formatReadFeatures),
                        _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
        } catch (RuntimeException e) {
            // 10-Jun-2022, tatu: For [core#763] may need to close InputStream here
            if (ioCtxt.isResourceManaged()) {
                try {
                    in.close();
                } catch (Exception e2) {
                    e.addSuppressed(e2);
                }
            }
            throw e;
        }
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            Reader r) throws JacksonException
    {
        return new ReaderBasedJsonParser(readCtxt, ioCtxt,
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                r,
                _rootCharSymbols.makeChild());
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            char[] data, int offset, int len,
            boolean recyclable) throws JacksonException
    {
        _checkRangeBoundsForCharArray(data, offset, len);
        return new ReaderBasedJsonParser(readCtxt, ioCtxt,
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                null,
                _rootCharSymbols.makeChild(),
                data, offset, offset+len, recyclable);
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            byte[] data, int offset, int len)
        throws JacksonException
    {
        _checkRangeBoundsForByteArray(data, offset, len);
        return new ByteSourceJsonBootstrapper(ioCtxt, data, offset, len)
                .constructParser(readCtxt,
                        readCtxt.getStreamReadFeatures(_streamReadFeatures),
                        readCtxt.getFormatReadFeatures(_formatReadFeatures),
                       _byteSymbolCanonicalizer, _rootCharSymbols, _factoryFeatures);
    }

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            DataInput input)
        throws JacksonException
    {
        // Also: while we can't do full bootstrapping (due to read-ahead limitations), should
        // at least handle possible UTF-8 BOM
        int firstByte = ByteSourceJsonBootstrapper.skipUTF8BOM(input);
        ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
        return new UTF8DataInputJsonParser(readCtxt, ioCtxt,
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                input, can, firstByte);
    }

    /*
    /**********************************************************************
    /* Factory methods used by factory for creating generator instances
    /**********************************************************************
     */

    @Override
    protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt,
            IOContext ioCtxt, Writer out)
        throws JacksonException
    {
        SerializableString rootSep = writeCtxt.getRootValueSeparator(_rootValueSeparator);
        // May get Character-Escape overrides from context; if not, use factory's own
        // (which default to `null`)
        CharacterEscapes charEsc = writeCtxt.getCharacterEscapes();
        if (charEsc == null) {
            charEsc = _characterEscapes;
        }
        // 14-Jan-2019, tatu: Should we make this configurable via databind layer?
        final int maxNonEscaped = _maximumNonEscapedChar;
        // NOTE: JSON generator does not use schema
        return new WriterBasedJsonGenerator(writeCtxt, ioCtxt,
                writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
                writeCtxt.getFormatWriteFeatures(_formatWriteFeatures),
                out,
                rootSep, writeCtxt.getPrettyPrinter(), charEsc, maxNonEscaped, _quoteChar);
    }

    @Override
    protected JsonGenerator _createUTF8Generator(ObjectWriteContext writeCtxt,
            IOContext ioCtxt, OutputStream out) throws JacksonException
    {
        SerializableString rootSep = writeCtxt.getRootValueSeparator(_rootValueSeparator);
        // May get Character-Escape overrides from context; if not, use factory's own
        // (which default to `null`)
        CharacterEscapes charEsc = writeCtxt.getCharacterEscapes();
        if (charEsc == null) {
            charEsc = _characterEscapes;
        }
        // 14-Jan-2019, tatu: Should we make this configurable via databind layer?
        final int maxNonEscaped = _maximumNonEscapedChar;
        // NOTE: JSON generator does not use schema

        return new UTF8JsonGenerator(writeCtxt, ioCtxt,
                writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
                writeCtxt.getFormatWriteFeatures(_formatWriteFeatures),
                out,
                rootSep, charEsc, writeCtxt.getPrettyPrinter(), maxNonEscaped, _quoteChar);
    }

    /*
    /**********************************************************************
    /* Other factory methods
    /**********************************************************************
     */

    @Override
    public PropertyNameMatcher constructNameMatcher(List<Named> matches, boolean alreadyInterned) {
        return BinaryNameMatcher.constructFrom(matches, alreadyInterned);
    }

    @Override
    public PropertyNameMatcher constructCINameMatcher(List<Named> matches, boolean alreadyInterned,
            Locale locale) {
        return BinaryNameMatcher.constructCaseInsensitive(locale, matches, alreadyInterned);
    }
}