JsonGeneratorBase.java

package tools.jackson.core.json;

import tools.jackson.core.*;
import tools.jackson.core.base.GeneratorBase;
import tools.jackson.core.io.CharTypes;
import tools.jackson.core.io.CharacterEscapes;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.util.JacksonFeatureSet;

/**
 * Intermediate base class shared by JSON-backed generators
 * like {@link UTF8JsonGenerator} and {@link WriterBasedJsonGenerator}.
 */
public abstract class JsonGeneratorBase extends GeneratorBase
{
    /*
    /**********************************************************************
    /* Constants
    /**********************************************************************
     */

    /**
     * This is the default set of escape codes, over 7-bit ASCII range
     * (first 128 character codes), used for single-byte UTF-8 characters.
     */
    protected final static int[] DEFAULT_OUTPUT_ESCAPES = CharTypes.get7BitOutputEscapes();

    /*
    /**********************************************************************
    /* Configuration, basic I/O, features
    /**********************************************************************
     */

    /**
     * Bit flag composed of bits that indicate which
     * {@link tools.jackson.core.json.JsonWriteFeature}s
     * are enabled.
     */
    protected final int _formatWriteFeatures;

    /*
    /**********************************************************************
    /* Configuration, output escaping
    /**********************************************************************
     */

    /**
     * Currently active set of output escape code definitions (whether
     * and how to escape or not) for 7-bit ASCII range (first 128
     * character codes). Defined separately to make potentially
     * customizable
     */
    protected int[] _outputEscapes = DEFAULT_OUTPUT_ESCAPES;

    /**
     * Definition of custom character escapes to use for generators created
     * by this factory, if any. If null, standard data format specific
     * escapes are used.
     *<p>
     * NOTE: although typically set during construction (in constructor),
     * cannot be made final in 3.0 due to some edge use cases (JSONP support).
     */
    protected CharacterEscapes _characterEscapes;

    /**
     * Value between 128 (0x80) and 65535 (0xFFFF) that indicates highest
     * Unicode code point that will not need escaping; or 0 to indicate
     * that all characters can be represented without escaping.
     * Typically used to force escaping of some portion of character set;
     * for example to always escape non-ASCII characters (if value was 127).
     *<p>
     * NOTE: not all sub-classes make use of this setting.
     */
    protected final int _maximumNonEscapedChar;

    /*
    /**********************************************************************
    /* Configuration, other
    /**********************************************************************
     */

    /**
     * Object that handles pretty-printing (usually additional white space to make
     * results more human-readable) during output. If {@code null}, no pretty-printing is
     * done.
     * <p>
     * NOTE: this may be {@link PrettyPrinter} that {@link TokenStreamFactory} was
     * configured with (if stateless), OR an instance created via
     * {@link tools.jackson.core.util.Instantiatable#createInstance()} (if
     * stateful). Either way, it is per-generator instance.
     *<p>
     * NOTE: in Jackson 2.x this field was called {@code _cfgPrettyPrinter}.
     */
    protected final PrettyPrinter _prettyPrinter;

    /**
     * Separator to use, if any, between root-level values.
     */
    protected final SerializableString _rootValueSeparator;

    /**
     * Flag that is set if quoting is not to be added around
     * JSON Object property names.
     */
    protected final boolean _cfgUnqNames;

    /**
     * Flag set to indicate that implicit conversion from number
     * to JSON String is needed (as per
     * {@link tools.jackson.core.json.JsonWriteFeature#WRITE_NUMBERS_AS_STRINGS}).
     */
    protected final boolean _cfgNumbersAsStrings;

    /**
     * Whether to write Hex values with upper-case letters (true)
     * or lower-case (false)
     */
    protected boolean _cfgWriteHexUppercase;

    /*
    /**********************************************************************
    /* Output state
    /**********************************************************************
     */

    /**
     * Object that keeps track of the current contextual state of the generator.
     */
    protected JsonWriteContext _streamWriteContext;

    /*
    /**********************************************************************
    /* Life-cycle
    /**********************************************************************
     */

    protected JsonGeneratorBase(ObjectWriteContext writeCtxt, IOContext ioCtxt,
            int streamWriteFeatures, int formatWriteFeatures,
            SerializableString rootValueSeparator, PrettyPrinter pp,
            CharacterEscapes charEsc, int maxNonEscaped)
    {
        super(writeCtxt, ioCtxt, streamWriteFeatures);
        _formatWriteFeatures = formatWriteFeatures;
        // By default we use this feature to determine additional quoting
        if (JsonWriteFeature.ESCAPE_NON_ASCII.enabledIn(formatWriteFeatures)) {
            // note! Lowest effective value is 127 (0 is used as marker, but values
            // from 1 through 126 have no effect different from 127), so:
            maxNonEscaped = 127;
        }
        _maximumNonEscapedChar = maxNonEscaped;
        _cfgUnqNames = !JsonWriteFeature.QUOTE_PROPERTY_NAMES.enabledIn(formatWriteFeatures);
        _cfgNumbersAsStrings = JsonWriteFeature.WRITE_NUMBERS_AS_STRINGS.enabledIn(formatWriteFeatures);
        _cfgWriteHexUppercase = JsonWriteFeature.WRITE_HEX_UPPER_CASE.enabledIn(formatWriteFeatures);
        _rootValueSeparator = rootValueSeparator;

        _prettyPrinter = pp;

        final DupDetector dups = StreamWriteFeature.STRICT_DUPLICATE_DETECTION.enabledIn(streamWriteFeatures)
                ? DupDetector.rootDetector(this) : null;
        _streamWriteContext = JsonWriteContext.createRootContext(dups);

        // 03-Oct-2017, tatu: Not clean (shouldn't call non-static methods from ctor),
        //    but for now best way to avoid code duplication
        setCharacterEscapes(charEsc);
    }

    /*
    /**********************************************************************
    /* Versioned, accessors, capabilities
    /**********************************************************************
     */

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

    /*
    /**********************************************************************
    /* Basic configuration access
    /**********************************************************************
     */
    
    public boolean isEnabled(JsonWriteFeature f) { return f.enabledIn(_formatWriteFeatures); }

    @Override
    public boolean has(StreamWriteCapability capability) {
        return DEFAULT_TEXTUAL_WRITE_CAPABILITIES.isEnabled(capability);
    }

    @Override
    public JacksonFeatureSet<StreamWriteCapability> streamWriteCapabilities() {
        return DEFAULT_TEXTUAL_WRITE_CAPABILITIES;
    }

    /*
    /**********************************************************************
    /* Overridden configuration methods
    /**********************************************************************
     */

    @Override
    public int getHighestNonEscapedChar() {
        return _maximumNonEscapedChar;
    }

    @Override
    public abstract JsonGenerator setCharacterEscapes(CharacterEscapes esc);

    /**
     * Method for accessing custom escapes factory uses for {@link JsonGenerator}s
     * it creates.
     */
    @Override
    public CharacterEscapes getCharacterEscapes() {
        return _characterEscapes;
    }

    @Override
    public PrettyPrinter getPrettyPrinter() {
        return _prettyPrinter;
    }

    /*
    /**********************************************************************
    /* Overridden output state handling methods
    /**********************************************************************
     */

    @Override
    public final TokenStreamContext streamWriteContext() { return _streamWriteContext; }

    @Override
    public final Object currentValue() {
        return _streamWriteContext.currentValue();
    }

    @Override
    public final void assignCurrentValue(Object v) {
        _streamWriteContext.assignCurrentValue(v);
    }

    /*
    /**********************************************************************
    /* Partial API, structural
    /**********************************************************************
     */

    @Override
    public JsonGenerator writeStartArray(Object currentValue, int size) throws JacksonException {
        writeStartArray(currentValue);
        return this;
    }

    @Override
    public JsonGenerator writeStartObject(Object currentValue, int size) throws JacksonException {
        writeStartObject(currentValue);
        return this;
    }

    /*
    /**********************************************************************
    /* Partial API, Object property names/ids
    /**********************************************************************
     */

    @Override
    public JsonGenerator writePropertyId(long id) throws JacksonException {
        writeName(Long.toString(id));
        return this;
    }

    /*
    /**********************************************************************
    /* Shared helper methods
    /**********************************************************************
     */

    protected void _verifyPrettyValueWrite(String typeMsg, int status) throws JacksonException
    {
        // If we have a pretty printer, it knows what to do:
        switch (status) {
        case JsonWriteContext.STATUS_OK_AFTER_COMMA: // array
            _prettyPrinter.writeArrayValueSeparator(this);
            break;
        case JsonWriteContext.STATUS_OK_AFTER_COLON:
            _prettyPrinter.writeObjectNameValueSeparator(this);
            break;
        case JsonWriteContext.STATUS_OK_AFTER_SPACE:
            _prettyPrinter.writeRootValueSeparator(this);
            break;
        case JsonWriteContext.STATUS_OK_AS_IS:
            // First entry, but of which context?
            if (_streamWriteContext.inArray()) {
                _prettyPrinter.beforeArrayValues(this);
            } else if (_streamWriteContext.inObject()) {
                _prettyPrinter.beforeObjectEntries(this);
            }
            break;
        case JsonWriteContext.STATUS_EXPECT_NAME:
            _reportCantWriteValueExpectName(typeMsg);
            break;
        default:
            _throwInternal();
            break;
        }
    }

    protected void _reportCantWriteValueExpectName(String typeMsg) throws JacksonException
    {
        throw _constructWriteException("Cannot %s, expecting a property name (context: %s)",
                typeMsg, _streamWriteContext.typeDesc());
    }
}