SmileFactory.java

package tools.jackson.dataformat.smile;

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

import tools.jackson.core.*;
import tools.jackson.core.base.BinaryTSFactory;
import tools.jackson.core.exc.StreamWriteException;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.sym.BinaryNameMatcher;
import tools.jackson.core.sym.ByteQuadsCanonicalizer;
import tools.jackson.core.sym.PropertyNameMatcher;
import tools.jackson.core.util.Named;
import tools.jackson.dataformat.smile.async.NonBlockingByteArrayParser;

/**
 * Factory used for constructing {@link SmileParser} and {@link SmileGenerator}
 * instances; both of which handle
 * <a href="http://wiki.fasterxml.com/SmileFormat">Smile</a> encoded data.
 *<p>
 * Extends {@link TokenStreamFactory} mostly so that users can actually use it in place
 * of regular non-Smile factory instances.
 *<p>
 * Note on using non-byte-based sources/targets (char based, like
 * {@link java.io.Reader} and {@link java.io.Writer}): these can not be
 * used for Smile-format documents, and thus will either downgrade to
 * textual JSON (when parsing), or throw exception (when trying to create
 * generator).
 */
public class SmileFactory
    extends BinaryTSFactory
    implements java.io.Serializable
{
    private static final long serialVersionUID = 1L;

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

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

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

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

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

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

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

    /**
     * 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 SmileFactory() {
        super(StreamReadConstraints.defaults(), StreamWriteConstraints.defaults(),
                ErrorReportConfiguration.defaults(),
                DEFAULT_SMILE_PARSER_FEATURE_FLAGS, DEFAULT_SMILE_GENERATOR_FEATURE_FLAGS);
    }

    public SmileFactory(SmileFactory src)
    {
        super(src);
    }

    /**
     * Constructors used by {@link SmileFactoryBuilder} for instantiation.
     *
     * @since 3.0
     */
    protected SmileFactory(SmileFactoryBuilder b) {
        super(b);
    }

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

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

    @Override
    public SmileFactory copy() {
        return new SmileFactory(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 SmileFactory(this);
    }

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

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

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

    /**
     * Checked whether specified parser feature is enabled.
     */
    public final boolean isEnabled(SmileReadFeature f) {
        return f.enabledIn(_formatReadFeatures);
    }

    /**
     * Check whether specified generator feature is enabled.
     */
    public final boolean isEnabled(SmileWriteFeature f) {
        return f.enabledIn(_formatWriteFeatures);
    }

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

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

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

    @Override
    public Class<SmileReadFeature> getFormatReadFeatureType() {
        return SmileReadFeature.class;
    }

    @Override
    public Class<SmileWriteFeature> getFormatWriteFeatureType() {
        return SmileWriteFeature.class;
    }

    /*
    /**********************************************************************
    /* Extended API: async
    /**********************************************************************
     */

    @SuppressWarnings("unchecked")
    @Override
    public NonBlockingByteArrayParser createNonBlockingByteArrayParser(ObjectReadContext readCtxt)
    {
        ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
        return new NonBlockingByteArrayParser(readCtxt, _createContext(null, false),
                readCtxt.getStreamReadFeatures(_streamReadFeatures),
                readCtxt.getFormatReadFeatures(_formatReadFeatures),
                can);
    }

    /*
    /**********************************************************************
    /* Factory method impls: parsers
    /**********************************************************************
     */

    /**
     * Overridable factory method that actually instantiates desired parser.
     */
    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            InputStream in)
    {
        return new SmileParserBootstrapper(ioCtxt, in)
            .constructParser(readCtxt, _factoryFeatures,
                    readCtxt.getStreamReadFeatures(_streamReadFeatures),
                    readCtxt.getFormatReadFeatures(_formatReadFeatures),
                    _byteSymbolCanonicalizer);
    }

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

    @Override
    protected JsonParser _createParser(ObjectReadContext readCtxt, IOContext ioCtxt,
            DataInput input) {
        // 30-Sep-2017, tatu: As of now not supported (but would be possible)
        return _unsupported();
    }

    /*
    /**********************************************************************
    /* Factory method impls: generators
    /**********************************************************************
     */

    @Override
    protected JsonGenerator _createGenerator(ObjectWriteContext writeCtxt,
            IOContext ioCtxt, OutputStream out)
    {
        int smileFeatures = writeCtxt.getFormatWriteFeatures(_formatWriteFeatures);
        /* One sanity check: MUST write header if shared string values setting is enabled,
         * or quoting of binary data disabled.
         * But should we force writing, or throw exception, if settings are in conflict?
         * For now, let's error out...
         */
        SmileGenerator gen = new SmileGenerator(writeCtxt, ioCtxt,
                writeCtxt.getStreamWriteFeatures(_streamWriteFeatures),
                smileFeatures, out);
        if (SmileWriteFeature.WRITE_HEADER.enabledIn(smileFeatures)) {
            gen.writeHeader();
        } else {
            if (SmileWriteFeature.CHECK_SHARED_STRING_VALUES.enabledIn(smileFeatures)) {
                throw new StreamWriteException(gen,
                        "Inconsistent settings: WRITE_HEADER disabled, but CHECK_SHARED_STRING_VALUES enabled; can not construct generator"
                        +" due to possible data loss (either enable WRITE_HEADER, or disable CHECK_SHARED_STRING_VALUES to resolve)");
            }
            if (!SmileWriteFeature.ENCODE_BINARY_AS_7BIT.enabledIn(smileFeatures)) {
                throw new StreamWriteException(gen,
        			"Inconsistent settings: WRITE_HEADER disabled, but ENCODE_BINARY_AS_7BIT disabled; can not construct generator"
        			+" due to possible data loss (either enable WRITE_HEADER, or ENCODE_BINARY_AS_7BIT to resolve)");
            }
        }
        return gen;
    }

    /*
    /**********************************************************************
    /* 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);
    }
}