ParserMinimalBase.java

package tools.jackson.core.base;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;

import tools.jackson.core.*;
import tools.jackson.core.exc.InputCoercionException;
import tools.jackson.core.exc.JacksonIOException;
import tools.jackson.core.exc.StreamConstraintsException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.exc.UnexpectedEndOfInputException;
import tools.jackson.core.io.IOContext;
import tools.jackson.core.io.NumberInput;
import tools.jackson.core.sym.PropertyNameMatcher;
import tools.jackson.core.type.ResolvedType;
import tools.jackson.core.type.TypeReference;
import tools.jackson.core.util.ByteArrayBuilder;
import tools.jackson.core.util.JacksonFeatureSet;
import tools.jackson.core.util.VersionUtil;

import static tools.jackson.core.JsonTokenId.*;

/**
 * Intermediate base class used by all Jackson {@link JsonParser}
 * implementations, but does not add any additional fields that depend
 * on particular method of obtaining input.
 *<p>
 * Note that 'minimal' here mostly refers to minimal number of fields
 * (size) and functionality that is specific to certain types
 * of parser implementations; but not necessarily to number of methods.
 */
public abstract class ParserMinimalBase extends JsonParser
{
    // Control chars:
    protected final static int INT_TAB = '\t';
    protected final static int INT_LF = '\n';
    protected final static int INT_CR = '\r';
    protected final static int INT_RS = 0x001E;
    protected final static int INT_SPACE = 0x0020;

    // Markup
    protected final static int INT_LBRACKET = '[';
    protected final static int INT_RBRACKET = ']';
    protected final static int INT_LCURLY = '{';
    protected final static int INT_RCURLY = '}';
    protected final static int INT_QUOTE = '"';
    protected final static int INT_APOS = '\'';
    protected final static int INT_BACKSLASH = '\\';
    protected final static int INT_SLASH = '/';
    protected final static int INT_ASTERISK = '*';
    protected final static int INT_COLON = ':';
    protected final static int INT_COMMA = ',';
    protected final static int INT_HASH = '#';

    // Number chars
    protected final static int INT_0 = '0';
    protected final static int INT_9 = '9';
    protected final static int INT_MINUS = '-';
    protected final static int INT_PLUS = '+';

    protected final static int INT_PERIOD = '.';
    protected final static int INT_e = 'e';
    protected final static int INT_E = 'E';

    protected final static char CHAR_NULL = '\0';

    protected final static byte[] NO_BYTES = new byte[0];

    protected final static int[] NO_INTS = new int[0];

    /*
    /**********************************************************************
    /* Constants and fields wrt number handling
    /**********************************************************************
     */

    protected final static int NR_UNKNOWN = 0;

    // First, integer types

    protected final static int NR_INT = 0x0001;
    protected final static int NR_LONG = 0x0002;
    protected final static int NR_BIGINT = 0x0004;

    // And then floating point types

    protected final static int NR_DOUBLE = 0x008;
    protected final static int NR_BIGDECIMAL = 0x0010;

    /**
     * NOTE! Not used by JSON implementation but used by many of binary codecs
     */
    protected final static int NR_FLOAT = 0x020;

    // Also, we need some numeric constants

    protected final static BigInteger BI_MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE);
    protected final static BigInteger BI_MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE);

    protected final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
    protected final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);

    protected final static BigDecimal BD_MIN_LONG = new BigDecimal(BI_MIN_LONG);
    protected final static BigDecimal BD_MAX_LONG = new BigDecimal(BI_MAX_LONG);

    protected final static BigDecimal BD_MIN_INT = new BigDecimal(BI_MIN_INT);
    protected final static BigDecimal BD_MAX_INT = new BigDecimal(BI_MAX_INT);

    protected final static int MIN_BYTE_I = (int) Byte.MIN_VALUE;
    // Allow range up to and including 255, to support signed AND unsigned bytes
    protected final static int MAX_BYTE_I = (int) 255;

    protected final static int MIN_SHORT_I = (int) Short.MIN_VALUE;
    protected final static int MAX_SHORT_I = (int) Short.MAX_VALUE;

    protected final static long MIN_INT_L = (long) Integer.MIN_VALUE;
    protected final static long MAX_INT_L = (long) Integer.MAX_VALUE;

    // These are not very accurate, but have to do... (for bounds checks)

    protected final static double MIN_LONG_D = (double) Long.MIN_VALUE;
    protected final static double MAX_LONG_D = (double) Long.MAX_VALUE;

    protected final static double MIN_INT_D = (double) Integer.MIN_VALUE;
    protected final static double MAX_INT_D = (double) Integer.MAX_VALUE;

    /*
    /**********************************************************************
    /* Misc other constants
    /**********************************************************************
     */

    protected final static int STREAM_READ_FEATURE_DEFAULTS = StreamReadFeature.collectDefaults();

    /*
    /**********************************************************************
    /* Minimal configuration state
    /**********************************************************************
     */

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

    /**
     * Constraints to use for this parser.
     */
    protected final StreamReadConstraints _streamReadConstraints;

    /*
    /**********************************************************************
    /* Minimal generally useful state
    /**********************************************************************
     */

    /**
     * Context object provided by higher level functionality like
     * databinding for two reasons: passing configuration information
     * during construction, and to allow calling of some databind
     * operations via parser instance.
     *
     * @since 3.0
     */
    protected final ObjectReadContext _objectReadContext;

    /**
     * I/O context for this reader. It handles buffer allocation
     * for the reader, including possible reuse/recycling.
     *
     * @since 3.0 (at this level)
     */
    protected final IOContext _ioContext;

    /**
     * Flag that indicates whether parser is closed or not. Gets
     * set when parser is either closed by explicit call
     * ({@link #close}) or when end-of-input is reached.
     */
    protected boolean _closed;
    
    /**
     * Last token retrieved via {@link #nextToken}, if any.
     * Null before the first call to <code>nextToken()</code>,
     * as well as if token has been explicitly cleared
     */
    protected JsonToken _currToken;

    /**
     * Last cleared token, if any: that is, value that was in
     * effect when {@link #clearCurrentToken} was called.
     */
    protected JsonToken _lastClearedToken;

    /**
     * Current count of tokens, if tracked (see {@link #_trackMaxTokenCount})
     */
    protected long _tokenCount;

    /**
     * Whether or not to track the token count due a {@link StreamReadConstraints} maxTokenCount > 0.
     */
    protected final boolean _trackMaxTokenCount;

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

    /**
     * Main constructor for sub-classes to use
     *
     * @param readCtxt Context for databinding
     * @param ioCtxt Context for I/O handling, buffering
     * @param streamReadFeatures Bit set of {@link StreamReadFeature}s.
     */
    protected ParserMinimalBase(ObjectReadContext readCtxt,
            IOContext ioCtxt, int streamReadFeatures)
    {
        super();
        _objectReadContext = readCtxt;
        _ioContext = ioCtxt;
        _streamReadFeatures = streamReadFeatures;
        _streamReadConstraints = ioCtxt.streamReadConstraints();
        _trackMaxTokenCount = _streamReadConstraints.hasMaxTokenCount();
    }

    /**
     * Alternate constructors for cases where there is no real {@link IOContext}
     * in use; typically for abstractions that operate over non-streaming/incremental
     * sources (such as jackson-databind {@code TokenBuffer}).
     * 
     * @param readCtxt Context for databinding
     */
    protected ParserMinimalBase(ObjectReadContext readCtxt) {
        super();
        _objectReadContext = readCtxt;
        _ioContext = null;
        _streamReadFeatures = readCtxt.getStreamReadFeatures(STREAM_READ_FEATURE_DEFAULTS);
        _streamReadConstraints = readCtxt.streamReadConstraints();
        _trackMaxTokenCount = _streamReadConstraints.hasMaxTokenCount();
    }
    
    /*
    /**********************************************************************
    /* Configuration overrides if any
    /**********************************************************************
     */

    // from base class:

    /*
    @Override
    public JsonParser enable(StreamReadFeature f) {
        _streamReadFeatures |= f.getMask();
        return this;
    }

    @Override
    public JsonParser disable(StreamReadFeature f) {
        _streamReadFeatures &= ~f.getMask();
        return this;
    }
    */

    @Override
    public boolean isEnabled(StreamReadFeature f) { return f.enabledIn(_streamReadFeatures); }

    /*
    /**********************************************************************
    /* Config access, capability introspection
    /**********************************************************************
     */

    @Override
    public JacksonFeatureSet<StreamReadCapability> streamReadCapabilities() {
        return DEFAULT_READ_CAPABILITIES;
    }

    @Override
    public int streamReadFeatures() {
        return _streamReadFeatures;
    }

    @Override
    public StreamReadConstraints streamReadConstraints() {
        return _streamReadConstraints;
    }

    /*
    /**********************************************************************
    /* JsonParser impl: open / close / release
    /**********************************************************************
     */

    // public JsonToken getCurrentToken()
    // public boolean hasCurrentToken()

    @Override
    public boolean isClosed() { return _closed; }

    @Override
    public void close() throws JacksonException
    {
        if (_closed) {
            return;
        }
        _closed = true;
        // 30-May-2025, tatu: was missing before 2.20
        if (StreamReadFeature.CLEAR_CURRENT_TOKEN_ON_CLOSE.enabledIn(_streamReadFeatures)) {
            _currToken = null;
        }

        // Ordering is important: needs to be done in following order:
        //
        // 1. Close actual input source that may use buffering
        // 2. Close (release) underlying buffers (to/via IOContext)
        // 3. Close IOContext which will then release recycler (if any)
        try {
            _closeInput();
        } catch (IOException e) {
            throw _wrapIOFailure(e);
        } finally {
            // Do in finally block so occurs even if stream closing fails
            // with exception
            _releaseBuffers();
            if (_ioContext != null) { // is case for some "virtual" parsers
                _ioContext.close();
            }
        }
    }

    /**
     * Abstract method for sub-classes to implement; to be called by
     * {@link #close()} implementation here.
     *
     * @throws IOException from underlying input source if thrown
     */
    protected abstract void _closeInput() throws IOException;

    /**
     * Method called to release internal buffers owned by the base
     * reader. This is expected to be called after {@link #_closeInput}
     * since the buffers are expected not to be needed any longer.
     */
    protected abstract void _releaseBuffers();

    /*
    /**********************************************************************
    /* JsonParser impl: basic state access
    /**********************************************************************
     */

    // public abstract TokenStreamContext getParsingContext();

    // public abstract TokenStreamLocation currentTokenLocation();
    // public abstract TokenStreamLocation currentLocation();

    @Override
    public ObjectReadContext objectReadContext() {
        return _objectReadContext;
    }

    /**
     * Method sub-classes need to implement to check whether end-of-content is allowed
     * at the current decoding position: formats often want to verify the all
     * start/end token pairs match, for example.
     *
     * @throws JacksonException if end-of-content not allowed at current position.
     */
    protected abstract void _handleEOF() throws JacksonException;

    // public abstract String currentName();

    /*
    /**********************************************************************
    /* JsonParser impl: basic stream iteration
    /**********************************************************************
     */

    // public abstract JsonToken nextToken() throws JacksonException;

    @Override public void finishToken() throws JacksonException { ; /* nothing */ }

    @Override public JsonToken currentToken() { return _currToken; }
    @Override public int currentTokenId() {
        final JsonToken t = _currToken;
        return (t == null) ? JsonTokenId.ID_NO_TOKEN : t.id();
    }

    @Override public boolean hasCurrentToken() { return _currToken != null; }
    @Override public boolean hasTokenId(int id) {
        final JsonToken t = _currToken;
        if (t == null) {
            return (JsonTokenId.ID_NO_TOKEN == id);
        }
        return t.id() == id;
    }

    @Override
    public boolean hasToken(JsonToken t) {
        return (_currToken == t);
    }

    @Override
    public long currentTokenCount() {
        return _tokenCount;
    }

    @Override public boolean isExpectedStartArrayToken() { return _currToken == JsonToken.START_ARRAY; }
    @Override public boolean isExpectedStartObjectToken() { return _currToken == JsonToken.START_OBJECT; }
    @Override public boolean isExpectedNumberIntToken() { return _currToken == JsonToken.VALUE_NUMBER_INT; }

    @Override
    public JsonToken nextValue() throws JacksonException {
        // Implementation should be as trivial as follows; only needs to change if
        // we are to skip other tokens (for example, if comments were exposed as tokens)
        JsonToken t = nextToken();
        if (t == JsonToken.PROPERTY_NAME) {
            t = nextToken();
        }
        return t;
    }

    @Override
    public JsonParser skipChildren() throws JacksonException
    {
        if (_currToken != JsonToken.START_OBJECT
            && _currToken != JsonToken.START_ARRAY) {
            return this;
        }
        int open = 1;

        // Since proper matching of start/end markers is handled
        // by nextToken(), we'll just count nesting levels here
        while (true) {
            JsonToken t = nextToken();
            if (t == null) {
                _handleEOF();
                // given constraints, above should never return; however, FindBugs
                // doesn't know about it and complains... so let's add dummy break here
                return this;
            }
            if (t.isStructStart()) {
                ++open;
            } else if (t.isStructEnd()) {
                if (--open == 0) {
                    return this;
                }
                // 23-May-2018, tatu: [core#463] Need to consider non-blocking case...
            } else if (t == JsonToken.NOT_AVAILABLE) {
                // Nothing much we can do except to either return `null` (which seems wrong),
                // or, what we actually do, signal error
                _reportError("Not enough content available for `skipChildren()`: non-blocking parser? (%s)",
                            getClass().getName());
            }
        }
    }

    /*
    /**********************************************************************
    /* JsonParser impl: stream iteration, property names
    /**********************************************************************
     */

    @Override
    public String nextName() throws JacksonException {
        return (nextToken() == JsonToken.PROPERTY_NAME) ? currentName() : null;
    }

    @Override
    public boolean nextName(SerializableString str) throws JacksonException {
        return (nextToken() == JsonToken.PROPERTY_NAME) && str.getValue().equals(currentName());
    }

    // Base implementation that should work well for most implementations but that
    // is typically overridden for performance optimization purposes
    @Override
    public int nextNameMatch(PropertyNameMatcher matcher) throws JacksonException {
        String str = nextName();
        if (str != null) {
            return matcher.matchName(str);
        }
        if (_currToken == JsonToken.END_OBJECT) {
            return PropertyNameMatcher.MATCH_END_OBJECT;
        }
        return PropertyNameMatcher.MATCH_ODD_TOKEN;
    }

    @Override
    public int currentNameMatch(PropertyNameMatcher matcher) {
        if (_currToken == JsonToken.PROPERTY_NAME) {
            return matcher.matchName(currentName());
        }
        if (_currToken == JsonToken.END_OBJECT) {
            return PropertyNameMatcher.MATCH_END_OBJECT;
        }
        return PropertyNameMatcher.MATCH_ODD_TOKEN;
    }

    /*
    /**********************************************************************
    /* Public API, token state overrides
    /**********************************************************************
     */

    @Override public void clearCurrentToken() {
        if (_currToken != null) {
            _lastClearedToken = _currToken;
            _currToken = null;
        }
    }

    @Override public JsonToken getLastClearedToken() { return _lastClearedToken; }

//    @Override public abstract void overrideCurrentName(String name);

    /*
    /**********************************************************************
    /* Public API, access to token information, text
    /**********************************************************************
     */

//    @Override public abstract String getText();
//    @Override public abstract char[] getTextCharacters();
//    @Override public abstract boolean hasTextCharacters();
//    @Override public abstract int getTextLength();
//    @Override public abstract int getTextOffset();

    @Override
    public int getString(Writer writer) throws JacksonException
    {
        String str = getString();
        if (str == null) {
            return 0;
        }
        try {
            writer.write(str);
        } catch (IOException e) {
            throw _wrapIOFailure(e);
        }
        return str.length();
    }

    /*
    /**********************************************************************
    /* Public API, access to token information, numeric
    /**********************************************************************
     */

//    public abstract Number getNumberValue();
//    public abstract NumberType getNumberType();

    @Override
    public NumberTypeFP getNumberTypeFP() {
        return NumberTypeFP.UNKNOWN;
    }

    @Override
    public Number getNumberValueExact() throws InputCoercionException {
        return getNumberValue();
    }

    @Override
    public Object getNumberValueDeferred() throws InputCoercionException {
        return getNumberValue();
    }

    @Override
    public byte getByteValue() throws InputCoercionException {
        int value = getIntValue();
        // So far so good: but does it fit?
        // Let's actually allow range of [-128, 255] instead of just signed range of [-128, 127]
        // since "unsigned" usage quite common for bytes (but Java may use signed range, too)
        if (value < MIN_BYTE_I || value > MAX_BYTE_I) {
            _reportOverflowByte(getString(), currentToken());
        }
        return (byte) value;
    }

    @Override
    public short getShortValue() throws InputCoercionException
    {
        int value = getIntValue();
        if (value < MIN_SHORT_I || value > MAX_SHORT_I) {
            _reportOverflowShort(getString(), currentToken());
        }
        return (short) value;
    }

//    public abstract int getIntValue();
//    public abstract long getLongValue();

    /*
    /**********************************************************************
    /* Public API, access to token information, other
    /**********************************************************************
     */

    @Override
    public boolean getBooleanValue() throws InputCoercionException {
        JsonToken t = currentToken();
        if (t == JsonToken.VALUE_TRUE) return true;
        if (t == JsonToken.VALUE_FALSE) return false;

        throw _constructInputCoercion(String.format("Current token (%s) not of boolean type", t),
            t, Boolean.TYPE);
    }

    /*
    /**********************************************************************
    /* Public API, access to token information, binary
    /**********************************************************************
     */

//    public abstract byte[] getBinaryValue(Base64Variant b64variant);

    @Override
    public Object getEmbeddedObject() { return null; }

    /*
    /**********************************************************************
    /* Public API, access with conversion/coercion
    /**********************************************************************
     */

    @Override
    public boolean getValueAsBoolean(boolean defaultValue)
    {
        JsonToken t = _currToken;
        if (t != null) {
            switch (t.id()) {
            case ID_STRING:
                String str = getString().trim();
                if ("true".equals(str)) {
                    return true;
                }
                if ("false".equals(str)) {
                    return false;
                }
                if (_hasTextualNull(str)) {
                    return false;
                }
                break;
            case ID_NUMBER_INT:
                return getIntValue() != 0;
            case ID_TRUE:
                return true;
            case ID_FALSE:
            case ID_NULL:
                return false;
            case ID_EMBEDDED_OBJECT:
                Object value = getEmbeddedObject();
                if (value instanceof Boolean) {
                    return (Boolean) value;
                }
                break;
            default:
            }
        }
        return defaultValue;
    }


    @Override
    public int getValueAsInt()
    {
        JsonToken t = _currToken;
        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
            return getIntValue();
        }
        return getValueAsInt(0);
    }

    @Override
    public int getValueAsInt(int defaultValue)
    {
        JsonToken t = _currToken;
        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
            return getIntValue();
        }
        if (t != null) {
            switch (t.id()) {
            case ID_STRING:
                String str = getString();
                if (_hasTextualNull(str)) {
                    return 0;
                }
                return NumberInput.parseAsInt(str, defaultValue);
            case ID_TRUE:
                return 1;
            case ID_FALSE:
                return 0;
            case ID_NULL:
                return 0;
            case ID_EMBEDDED_OBJECT:
                Object value = getEmbeddedObject();
                if (value instanceof Number) {
                    return ((Number) value).intValue();
                }
            }
        }
        return defaultValue;
    }

    @Override
    public long getValueAsLong()
    {
        JsonToken t = _currToken;
        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
            return getLongValue();
        }
        return getValueAsLong(0L);
    }

    @Override
    public long getValueAsLong(long defaultValue)
    {
        JsonToken t = _currToken;
        if ((t == JsonToken.VALUE_NUMBER_INT) || (t == JsonToken.VALUE_NUMBER_FLOAT)) {
            return getLongValue();
        }
        if (t != null) {
            switch (t.id()) {
            case ID_STRING:
                String str = getString();
                if (_hasTextualNull(str)) {
                    return 0L;
                }
                return NumberInput.parseAsLong(str, defaultValue);
            case ID_TRUE:
                return 1L;
            case ID_FALSE:
            case ID_NULL:
                return 0L;
            case ID_EMBEDDED_OBJECT:
                Object value = getEmbeddedObject();
                if (value instanceof Number) {
                    return ((Number) value).longValue();
                }
            }
        }
        return defaultValue;
    }

    @Override
    public double getValueAsDouble(double defaultValue)
    {
        JsonToken t = _currToken;
        if (t != null) {
            switch (t.id()) {
            case ID_STRING:
                String str = getString();
                if (_hasTextualNull(str)) {
                    return 0L;
                }
                return NumberInput.parseAsDouble(str, defaultValue,
                        isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
            case ID_NUMBER_INT:
            case ID_NUMBER_FLOAT:
                return getDoubleValue();
            case ID_TRUE:
                return 1.0;
            case ID_FALSE:
            case ID_NULL:
                return 0.0;
            case ID_EMBEDDED_OBJECT:
                Object value = getEmbeddedObject();
                if (value instanceof Number) {
                    return ((Number) value).doubleValue();
                }
            }
        }
        return defaultValue;
    }

    @Override
    public String getValueAsString() {
        // sub-classes tend to override so...
        return getValueAsString(null);
    }

    @Override
    public String getValueAsString(String defaultValue) {
        if (_currToken == JsonToken.VALUE_STRING) {
            return getString();
        }
        if (_currToken == JsonToken.PROPERTY_NAME) {
            return currentName();
        }
        if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) {
            return defaultValue;
        }
        return getString();
    }

    /*
    /**********************************************************************
    /* Databind callbacks
    /**********************************************************************
     */

    @Override
    public <T> T readValueAs(Class<T> valueType) throws JacksonException {
        return _objectReadContext.readValue(this, valueType);
    }

    @Override
    public <T> T readValueAs(TypeReference<T> valueTypeRef) throws JacksonException {
        return _objectReadContext.readValue(this, valueTypeRef);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T readValueAs(ResolvedType type) throws JacksonException {
        return (T) _objectReadContext.readValue(this, type);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends TreeNode> T readValueAsTree() throws JacksonException {
        return (T) _objectReadContext.readTree(this);
    }

    /*
    /**********************************************************************
    /* Helper methods: Base64 decoding
    /**********************************************************************
     */

    /**
     * Helper method that can be used for base64 decoding in cases where
     * encoded content has already been read as a String.
     *
     * @param str String to decode
     * @param builder Builder used to buffer binary content decoded
     * @param b64variant Base64 variant expected in content
     *
     * @throws JacksonIOException for low-level read issues
     * @throws StreamReadException for decoding problems
     */
    protected void _decodeBase64(String str, ByteArrayBuilder builder, Base64Variant b64variant)
        throws JacksonException
    {
        try {
            b64variant.decode(str, builder);
        } catch (IllegalArgumentException e) {
            _reportError(e.getMessage());
        }
    }

    /*
    /**********************************************************************
    /* Coercion helper methods (overridable)
    /**********************************************************************
     */

    /**
     * Helper method used to determine whether we are currently pointing to
     * a String value of "null" (NOT a null token); and, if so, that parser
     * is to recognize and return it similar to if it was real null token.
     *<p>
     * Default implementation accepts exact string {@code "null"} and nothing else
     *
     * @param value String value to check
     *
     * @return True if given value contains "null equivalent" String value (for
     *   content this parser handles).
     */
    protected boolean _hasTextualNull(String value) { return "null".equals(value); }

    /*
    /**********************************************************************
    /* Error reporting, numeric conversion/parsing issues
    /**********************************************************************
     */

    /**
     * Method called to throw an exception for input token that looks like a number
     * based on first character(s), but is not valid according to rules of format.
     * In case of JSON this also includes invalid forms like positive sign and
     * leading zeroes.
     *
     * @param <T> Nominal type parameter for bogus return value
     * @param msg Base exception message to use
     *
     * @return Nothing: never returns; declared so caller can use fake return
     *   to keep compiler happy
     *
     * @throws StreamReadException Exception that describes problem with number validity
     */
    protected <T> T _reportInvalidNumber(String msg) throws StreamReadException {
        return _reportError("Invalid numeric value: "+msg);
    }

    protected void _reportOverflowByte(String numDesc, JsonToken inputType) throws InputCoercionException {
        throw _constructInputCoercion(String.format(
                "Numeric value (%s) out of range of `byte` (%d - %s)",
                _longIntegerDesc(numDesc), MIN_BYTE_I, MAX_BYTE_I),
                inputType, Byte.TYPE);
    }

    protected void _reportOverflowShort(String numDesc, JsonToken inputType) throws InputCoercionException {
        throw _constructInputCoercion(String.format(
                "Numeric value (%s) out of range of `short` (%d - %s)",
                _longIntegerDesc(numDesc), MIN_SHORT_I, MAX_SHORT_I),
                inputType, Short.TYPE);
    }

    /**
     * Method called to throw an exception for integral (not floating point) input
     * token with value outside of Java signed 32-bit range when requested as {@code int}.
     * Result will be {@link InputCoercionException} being thrown.
     *
     * @throws InputCoercionException Exception that describes problem with number range validity
     */
    protected void _reportOverflowInt() throws InputCoercionException {
        _reportOverflowInt(getString());
    }

    protected void _reportOverflowInt(String numDesc) throws InputCoercionException {
        _reportOverflowInt(numDesc, currentToken());
    }

    protected void _reportOverflowInt(String numDesc, JsonToken inputType) throws InputCoercionException {
        throw _constructInputCoercion(String.format(
                "Numeric value (%s) out of range of `int` (%d - %s)",
                _longIntegerDesc(numDesc), Integer.MIN_VALUE, Integer.MAX_VALUE),
                inputType, Integer.TYPE);
    }

    /**
     * Method called to throw an exception for integral (not floating point) input
     * token with value outside of Java signed 64-bit range when requested as {@code long}.
     * Result will be {@link InputCoercionException} being thrown.
     *
     * @throws InputCoercionException Exception that describes problem with number range validity
     */
    protected void _reportOverflowLong() throws InputCoercionException {
        _reportOverflowLong(getString());
    }

    protected void _reportOverflowLong(String numDesc) throws InputCoercionException {
        _reportOverflowLong(numDesc, currentToken());
    }

    protected void _reportOverflowLong(String numDesc, JsonToken inputType) throws InputCoercionException {
        throw _constructInputCoercion(String.format(
                "Numeric value (%s) out of range of `long` (%d - %s)",
                _longIntegerDesc(numDesc), Long.MIN_VALUE, Long.MAX_VALUE),
                inputType, Long.TYPE);
    }

    protected String _longIntegerDesc(String rawNum) {
        int rawLen = rawNum.length();
        if (rawLen < 1000) {
            return rawNum;
        }
        if (rawNum.startsWith("-")) {
            rawLen -= 1;
        }
        return String.format("[Integer with %d digits]", rawLen);
    }

    protected String _longNumberDesc(String rawNum) {
        int rawLen = rawNum.length();
        if (rawLen < 1000) {
            return rawNum;
        }
        if (rawNum.startsWith("-")) {
            rawLen -= 1;
        }
        return String.format("[number with %d characters]", rawLen);
    }

    /*
    /**********************************************************************
    /* Error reporting, EOF, unexpected chars/content
    /**********************************************************************
     */

    // @since 3.0
    protected <T> T _reportBadInputStream(int readLen) throws StreamReadException
    {
        // 12-Jan-2021, tatu: May need to think about this bit more but for now
        //    do double-wrapping
        throw _wrapIOFailure(new IOException(
"Bad input source: InputStream.read() returned 0 bytes when trying to read "+readLen+" bytes"));
    }

    // @since 3.0
    protected <T> T _reportBadReader(int readLen) throws StreamReadException
    {
        // 12-Jan-2021, tatu: May need to think about this bit more but for now
        //    do double-wrapping
        throw _wrapIOFailure(new IOException(
"Bad input source: Reader.read() returned 0 bytes when trying to read "+readLen+" bytes"));
    }

    protected <T> T _reportInvalidEOF() throws StreamReadException {
        return _reportInvalidEOF(" in "+_currToken, _currToken);
    }

    protected <T> T _reportInvalidEOFInValue(JsonToken type) throws StreamReadException {
        String msg;
        if (type == JsonToken.VALUE_STRING) {
            msg = " in a String value";
        } else if ((type == JsonToken.VALUE_NUMBER_INT)
                || (type == JsonToken.VALUE_NUMBER_FLOAT)) {
            msg = " in a Number value";
        } else {
            msg = " in a value";
        }
        return _reportInvalidEOF(msg, type);
    }

    protected <T> T _reportInvalidEOF(String msg, JsonToken currToken) throws StreamReadException {
        throw new UnexpectedEndOfInputException(this, currToken, "Unexpected end-of-input"+msg);
    }

    protected <T> T _reportInvalidSpace(int i) throws StreamReadException {
        char c = (char) i;
        String msg = "Illegal character ("+_getCharDesc(c)
            +"): only regular white space (\\r, \\n, \\t) is allowed between tokens";
        if (i == INT_RS) {
            msg += " (consider enabling `JsonReadFeature.ALLOW_RS_CONTROL_CHAR` to allow use of Record Separators (\\u001E))";
        }
        throw _constructReadException(msg, _currentLocationMinusOne());
    }

    protected <T> T _reportMissingRootWS(int ch) throws StreamReadException {
        return _reportUnexpectedChar(ch, "Expected space separating root-level values");
    }

    protected <T> T _reportUnexpectedChar(int ch, String comment) throws StreamReadException
    {
        if (ch < 0) { // sanity check
            _reportInvalidEOF();
        }
        String msg = String.format("Unexpected character (%s)", _getCharDesc(ch));
        if (comment != null) {
            msg += ": "+comment;
        }
        throw _constructReadException(msg, _currentLocationMinusOne());
    }

    protected <T> T _reportUnexpectedNumberChar(int ch, String comment) throws StreamReadException {
        String msg = String.format("Unexpected character (%s) in numeric value", _getCharDesc(ch));
        if (comment != null) {
            msg += ": "+comment;
        }
        throw _constructReadException(msg, _currentLocationMinusOne());
    }

    /**
     * Method called to throw an exception for invalid UTF-8 surrogate character: case
     * where a surrogate character (between U+D800 and U+DFFF) is decoded from UTF-8
     * bytes (but NOT from JSON entity!)
     *
     * @param ch Character code (int) that is invalid surrogate
     *
     * @throws StreamReadException Exception that describes problem with UTF-8 surrogate
     */
    protected void _reportInvalidUTF8Surrogate(int ch) throws StreamReadException {
        throw _constructReadException(
                "Invalid UTF-8: Illegal surrogate character 0x"+Integer.toHexString(ch));
    }

    /**
     * Factory method used to provide location for cases where we must read
     * and consume a single "wrong" character (to possibly allow error recovery),
     * but need to report accurate location for that character: if so, the
     * current location is past location we want, and location we want will be
     * "one location earlier".
     *<p>
     * Default implementation simply returns {@link #currentLocation()}
     *
     * @since 2.17
     *
     * @return Same as {@link #currentLocation()} except offset by -1
     */
    protected TokenStreamLocation _currentLocationMinusOne() {
        return currentLocation();
    }

    protected final static String _getCharDesc(int ch)
    {
        char c = (char) ch;
        if (Character.isISOControl(c)) {
            return "(CTRL-CHAR, code "+ch+")";
        }
        if (ch > 255) {
            return "'"+c+"' (code "+ch+" / 0x"+Integer.toHexString(ch)+")";
        }
        return "'"+c+"' (code "+ch+")";
    }

    /*
    /**********************************************************************
    /* Error reporting, input coercion support
    /**********************************************************************
     */

    // @since 3.0
    protected InputCoercionException _constructNotNumericType(JsonToken actualToken, int expNumericType)
    {
        final String msg = String.format(
"Current token (%s) not numeric, cannot use numeric value accessors", actualToken);

        Class<?> targetType;

        switch (expNumericType) {
        case NR_INT:
            targetType = Integer.TYPE;
            break;
        case NR_LONG:
            targetType = Long.TYPE;
            break;
        case NR_BIGINT:
            targetType = BigInteger.class;
            break;
        case NR_FLOAT:
            targetType = Float.TYPE;
            break;
        case NR_DOUBLE:
            targetType = Double.TYPE;
            break;
        case NR_BIGDECIMAL:
            targetType = BigDecimal.class;
            break;
        default:
            targetType = Number.class;
            break;
        }
        return _constructInputCoercion(msg, actualToken, targetType);
    }

    protected InputCoercionException _constructInputCoercion(String msg, JsonToken inputType, Class<?> targetType) {
        return new InputCoercionException(this, msg, inputType, targetType);
    }

    /*
    /**********************************************************************
    /* Error reporting, generic
    /**********************************************************************
     */

    protected <T> T _reportError(String msg) throws StreamReadException {
        throw _constructReadException(msg);
   }

    protected <T> T _reportError(String msg, Object arg) throws StreamReadException {
        throw _constructReadException(String.format(msg, arg));
    }

    protected <T> T _reportError(String msg, Object arg1, Object arg2) throws StreamReadException {
        throw _constructReadException(String.format(msg, arg1, arg2));
    }

    protected <T> T _reportError(String msg, Object arg1, Object arg2, Object arg3) throws StreamReadException {
        throw _constructReadException(String.format(msg, arg1, arg2, arg3));
    }

    // @since 3.0
    protected JacksonException _wrapIOFailure(IOException e) {
        return JacksonIOException.construct(e, this);
    }

    protected <T> T _throwInternal() {
        return VersionUtil.throwInternalReturnAny();
    }

    /*
    /**********************************************************
    /* Helper methods, other
    /**********************************************************
     */

    // for performance reasons, this method assumes that the input token is non-null
    protected final JsonToken _updateToken(final JsonToken token)
        throws StreamConstraintsException
    {
        _currToken = token;
        if (_trackMaxTokenCount) {
            _streamReadConstraints.validateTokenCount(++_tokenCount);
        }
        return token;
    }

    // only updates the token count if input token is non-null
    protected final JsonToken _nullSafeUpdateToken(final JsonToken token)
        throws StreamConstraintsException
    {
        _currToken = token;
        if (_trackMaxTokenCount && token != null) {
            _streamReadConstraints.validateTokenCount(++_tokenCount);
        }
        return token;
    }

    protected final JsonToken _updateTokenToNull() {
        return (_currToken = null);
    }

    protected final JsonToken _updateTokenToNA() {
        return (_currToken = JsonToken.NOT_AVAILABLE);
    }
}