package com.fasterxml.jackson.core.base;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberInput;
import com.fasterxml.jackson.core.json.DupDetector;
import com.fasterxml.jackson.core.json.JsonReadContext;
import com.fasterxml.jackson.core.json.PackageVersion;
import com.fasterxml.jackson.core.util.ByteArrayBuilder;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.core.util.TextBuffer;

Intermediate base class used by all Jackson JsonParser implementations. Contains most common things that are independent of actual underlying input source.
/** * Intermediate base class used by all Jackson {@link JsonParser} * implementations. Contains most common things that are independent * of actual underlying input source. */
public abstract class ParserBase extends ParserMinimalBase { // JSON capabilities are the same as defaults // @since 2.12 protected final static JacksonFeatureSet<StreamReadCapability> JSON_READ_CAPABILITIES = DEFAULT_READ_CAPABILITIES; /* /********************************************************** /* Generic I/O state /********************************************************** */
I/O context for this reader. It handles buffer allocation for the reader.
/** * I/O context for this reader. It handles buffer allocation * for the reader. */
final protected IOContext _ioContext;
Flag that indicates whether parser is closed or not. Gets set when parser is either closed by explicit call (close) or when end-of-input is reached.
/** * 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; /* /********************************************************** /* Current input data /********************************************************** */ // Note: type of actual buffer depends on sub-class, can't include
Pointer to next available character in buffer
/** * Pointer to next available character in buffer */
protected int _inputPtr;
Index of character after last available one in the buffer.
/** * Index of character after last available one in the buffer. */
protected int _inputEnd; /* /********************************************************** /* Current input location information /********************************************************** */
Number of characters/bytes that were contained in previous blocks (blocks that were already processed prior to the current buffer).
/** * Number of characters/bytes that were contained in previous blocks * (blocks that were already processed prior to the current buffer). */
protected long _currInputProcessed;
Current row location of current point in input buffer, starting from 1, if available.
/** * Current row location of current point in input buffer, starting * from 1, if available. */
protected int _currInputRow = 1;
Current index of the first character of the current row in input buffer. Needed to calculate column position, if necessary; benefit of not having column itself is that this only has to be updated once per line.
/** * Current index of the first character of the current row in input * buffer. Needed to calculate column position, if necessary; benefit * of not having column itself is that this only has to be updated * once per line. */
protected int _currInputRowStart; /* /********************************************************** /* Information about starting location of event /* Reader is pointing to; updated on-demand /********************************************************** */ // // // Location info at point when current token was started
Total number of bytes/characters read before start of current token. For big (gigabyte-sized) sizes are possible, needs to be long, unlike pointers and sizes related to in-memory buffers.
/** * Total number of bytes/characters read before start of current token. * For big (gigabyte-sized) sizes are possible, needs to be long, * unlike pointers and sizes related to in-memory buffers. */
protected long _tokenInputTotal;
Input row on which current token starts, 1-based
/** * Input row on which current token starts, 1-based */
protected int _tokenInputRow = 1;
Column on input row that current token starts; 0-based (although in the end it'll be converted to 1-based)
/** * Column on input row that current token starts; 0-based (although * in the end it'll be converted to 1-based) */
protected int _tokenInputCol; /* /********************************************************** /* Parsing state /********************************************************** */
Information about parser context, context in which the next token is to be parsed (root, array, object).
/** * Information about parser context, context in which * the next token is to be parsed (root, array, object). */
protected JsonReadContext _parsingContext;
Secondary token related to the next token after current one; used if its type is known. This may be value token that follows FIELD_NAME, for example.
/** * Secondary token related to the next token after current one; * used if its type is known. This may be value token that * follows FIELD_NAME, for example. */
protected JsonToken _nextToken; /* /********************************************************** /* Buffer(s) for local name(s) and text content /********************************************************** */
Buffer that contains contents of String values, including field names if necessary (name split across boundary, contains escape sequence, or access needed to char array)
/** * Buffer that contains contents of String values, including * field names if necessary (name split across boundary, * contains escape sequence, or access needed to char array) */
protected final TextBuffer _textBuffer;
Temporary buffer that is needed if field name is accessed using ParserMinimalBase.getTextCharacters method (instead of String returning alternatives)
/** * Temporary buffer that is needed if field name is accessed * using {@link #getTextCharacters} method (instead of String * returning alternatives) */
protected char[] _nameCopyBuffer;
Flag set to indicate whether the field name is available from the name copy buffer or not (in addition to its String representation being available via read context)
/** * Flag set to indicate whether the field name is available * from the name copy buffer or not (in addition to its String * representation being available via read context) */
protected boolean _nameCopied;
ByteArrayBuilder is needed if 'getBinaryValue' is called. If so, we better reuse it for remainder of content.
/** * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so, * we better reuse it for remainder of content. */
protected ByteArrayBuilder _byteArrayBuilder;
We will hold on to decoded binary data, for duration of current event, so that multiple calls to JsonParser.getBinaryValue will not need to decode data more than once.
/** * We will hold on to decoded binary data, for duration of * current event, so that multiple calls to * {@link #getBinaryValue} will not need to decode data more * than once. */
protected byte[] _binaryValue; // Numeric value holders: multiple fields used for // for efficiency
Bitfield that indicates which numeric representations have been calculated for the current type
/** * Bitfield that indicates which numeric representations * have been calculated for the current type */
protected int _numTypesValid = NR_UNKNOWN; // First primitives protected int _numberInt; protected long _numberLong; protected double _numberDouble; // And then object types protected BigInteger _numberBigInt; protected BigDecimal _numberBigDecimal; // And then other information about value itself
Flag that indicates whether numeric value has a negative value. That is, whether its textual representation starts with minus character.
/** * Flag that indicates whether numeric value has a negative * value. That is, whether its textual representation starts * with minus character. */
protected boolean _numberNegative;
Length of integer part of the number, in characters
/** * Length of integer part of the number, in characters */
protected int _intLength;
Length of the fractional part (not including decimal point or exponent), in characters. Not used for pure integer values.
/** * Length of the fractional part (not including decimal * point or exponent), in characters. * Not used for pure integer values. */
protected int _fractLength;
Length of the exponent part of the number, if any, not including 'e' marker or sign, just digits. Not used for pure integer values.
/** * Length of the exponent part of the number, if any, not * including 'e' marker or sign, just digits. * Not used for pure integer values. */
protected int _expLength; /* /********************************************************** /* Life-cycle /********************************************************** */ protected ParserBase(IOContext ctxt, int features) { super(features); _ioContext = ctxt; _textBuffer = ctxt.constructTextBuffer(); DupDetector dups = Feature.STRICT_DUPLICATE_DETECTION.enabledIn(features) ? DupDetector.rootDetector(this) : null; _parsingContext = JsonReadContext.createRootContext(dups); } @Override public Version version() { return PackageVersion.VERSION; } @Override public Object getCurrentValue() { return _parsingContext.getCurrentValue(); } @Override public void setCurrentValue(Object v) { _parsingContext.setCurrentValue(v); } /* /********************************************************** /* Overrides for Feature handling /********************************************************** */ @Override public JsonParser enable(Feature f) { _features |= f.getMask(); if (f == Feature.STRICT_DUPLICATE_DETECTION) { // enabling dup detection? if (_parsingContext.getDupDetector() == null) { // but only if disabled currently _parsingContext = _parsingContext.withDupDetector(DupDetector.rootDetector(this)); } } return this; } @Override public JsonParser disable(Feature f) { _features &= ~f.getMask(); if (f == Feature.STRICT_DUPLICATE_DETECTION) { _parsingContext = _parsingContext.withDupDetector(null); } return this; } @Override @Deprecated public JsonParser setFeatureMask(int newMask) { int changes = (_features ^ newMask); if (changes != 0) { _features = newMask; _checkStdFeatureChanges(newMask, changes); } return this; } @Override // since 2.7 public JsonParser overrideStdFeatures(int values, int mask) { int oldState = _features; int newState = (oldState & ~mask) | (values & mask); int changed = oldState ^ newState; if (changed != 0) { _features = newState; _checkStdFeatureChanges(newState, changed); } return this; }
Helper method called to verify changes to standard features.
Params:
  • newFeatureFlags – Bitflag of standard features after they were changed
  • changedFeatures – Bitflag of standard features for which setting did change
Since:2.7
/** * Helper method called to verify changes to standard features. * * @param newFeatureFlags Bitflag of standard features after they were changed * @param changedFeatures Bitflag of standard features for which setting * did change * * @since 2.7 */
protected void _checkStdFeatureChanges(int newFeatureFlags, int changedFeatures) { int f = Feature.STRICT_DUPLICATE_DETECTION.getMask(); if ((changedFeatures & f) != 0) { if ((newFeatureFlags & f) != 0) { if (_parsingContext.getDupDetector() == null) { _parsingContext = _parsingContext.withDupDetector(DupDetector.rootDetector(this)); } else { // disabling _parsingContext = _parsingContext.withDupDetector(null); } } } } /* /********************************************************** /* JsonParser impl /********************************************************** */
Method that can be called to get the name associated with the current event.
/** * Method that can be called to get the name associated with * the current event. */
@Override public String getCurrentName() throws IOException { // [JACKSON-395]: start markers require information from parent if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { JsonReadContext parent = _parsingContext.getParent(); if (parent != null) { return parent.getCurrentName(); } } return _parsingContext.getCurrentName(); } @Override public void overrideCurrentName(String name) { // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing: JsonReadContext ctxt = _parsingContext; if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { ctxt = ctxt.getParent(); } // 24-Sep-2013, tatu: Unfortunate, but since we did not expose exceptions, // need to wrap this here try { ctxt.setCurrentName(name); } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void close() throws IOException { if (!_closed) { // 19-Jan-2018, tatu: as per [core#440] need to ensure no more data assumed available _inputPtr = Math.max(_inputPtr, _inputEnd); _closed = true; try { _closeInput(); } finally { // as per [JACKSON-324], do in finally block // Also, internal buffer(s) can now be released as well _releaseBuffers(); } } } @Override public boolean isClosed() { return _closed; } @Override public JsonReadContext getParsingContext() { return _parsingContext; }
Method that return the starting location of the current token; that is, position of the first character from input that starts the current token.
/** * Method that return the <b>starting</b> location of the current * token; that is, position of the first character from input * that starts the current token. */
@Override public JsonLocation getTokenLocation() { return new JsonLocation(_getSourceReference(), -1L, getTokenCharacterOffset(), // bytes, chars getTokenLineNr(), getTokenColumnNr()); }
Method that returns location of the last processed character; usually for error reporting purposes
/** * Method that returns location of the last processed character; * usually for error reporting purposes */
@Override public JsonLocation getCurrentLocation() { int col = _inputPtr - _currInputRowStart + 1; // 1-based return new JsonLocation(_getSourceReference(), -1L, _currInputProcessed + _inputPtr, // bytes, chars _currInputRow, col); } /* /********************************************************** /* Public API, access to token information, text and similar /********************************************************** */ @Override public boolean hasTextCharacters() { if (_currToken == JsonToken.VALUE_STRING) { return true; } // usually true if (_currToken == JsonToken.FIELD_NAME) { return _nameCopied; } return false; } @SuppressWarnings("resource") @Override // since 2.7 public byte[] getBinaryValue(Base64Variant variant) throws IOException { if (_binaryValue == null) { if (_currToken != JsonToken.VALUE_STRING) { _reportError("Current token ("+_currToken+") not VALUE_STRING, can not access as binary"); } ByteArrayBuilder builder = _getByteArrayBuilder(); _decodeBase64(getText(), builder, variant); _binaryValue = builder.toByteArray(); } return _binaryValue; } /* /********************************************************** /* Public low-level accessors /********************************************************** */ public long getTokenCharacterOffset() { return _tokenInputTotal; } public int getTokenLineNr() { return _tokenInputRow; } public int getTokenColumnNr() { // note: value of -1 means "not available"; otherwise convert from 0-based to 1-based int col = _tokenInputCol; return (col < 0) ? col : (col + 1); } /* /********************************************************** /* Abstract methods for sub-classes to implement /********************************************************** */ protected abstract void _closeInput() throws IOException; /* /********************************************************** /* Low-level reading, other /********************************************************** */
Method called to release internal buffers owned by the base reader. This may be called along with _closeInput (for example, when explicitly closing this reader instance), or separately (if need be).
/** * Method called to release internal buffers owned by the base * reader. This may be called along with {@link #_closeInput} (for * example, when explicitly closing this reader instance), or * separately (if need be). */
protected void _releaseBuffers() throws IOException { _textBuffer.releaseBuffers(); char[] buf = _nameCopyBuffer; if (buf != null) { _nameCopyBuffer = null; _ioContext.releaseNameCopyBuffer(buf); } }
Method called when an EOF is encountered between tokens. If so, it may be a legitimate EOF, but only iff there is no open non-root context.
/** * Method called when an EOF is encountered between tokens. * If so, it may be a legitimate EOF, but only iff there * is no open non-root context. */
@Override protected void _handleEOF() throws JsonParseException { if (!_parsingContext.inRoot()) { String marker = _parsingContext.inArray() ? "Array" : "Object"; _reportInvalidEOF(String.format( ": expected close marker for %s (start marker at %s)", marker, _parsingContext.getStartLocation(_getSourceReference())), null); } }
Since:2.4
/** * @since 2.4 */
protected final int _eofAsNextChar() throws JsonParseException { _handleEOF(); return -1; } /* /********************************************************** /* Internal/package methods: shared/reusable builders /********************************************************** */ public ByteArrayBuilder _getByteArrayBuilder() { if (_byteArrayBuilder == null) { _byteArrayBuilder = new ByteArrayBuilder(); } else { _byteArrayBuilder.reset(); } return _byteArrayBuilder; } /* /********************************************************** /* Methods from former JsonNumericParserBase /********************************************************** */ // // // Life-cycle of number-parsing protected final JsonToken reset(boolean negative, int intLen, int fractLen, int expLen) { if (fractLen < 1 && expLen < 1) { // integer return resetInt(negative, intLen); } return resetFloat(negative, intLen, fractLen, expLen); } protected final JsonToken resetInt(boolean negative, int intLen) { _numberNegative = negative; _intLength = intLen; _fractLength = 0; _expLength = 0; _numTypesValid = NR_UNKNOWN; // to force parsing return JsonToken.VALUE_NUMBER_INT; } protected final JsonToken resetFloat(boolean negative, int intLen, int fractLen, int expLen) { _numberNegative = negative; _intLength = intLen; _fractLength = fractLen; _expLength = expLen; _numTypesValid = NR_UNKNOWN; // to force parsing return JsonToken.VALUE_NUMBER_FLOAT; } protected final JsonToken resetAsNaN(String valueStr, double value) { _textBuffer.resetWithString(valueStr); _numberDouble = value; _numTypesValid = NR_DOUBLE; return JsonToken.VALUE_NUMBER_FLOAT; } @Override public boolean isNaN() { if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) { if ((_numTypesValid & NR_DOUBLE) != 0) { // 10-Mar-2017, tatu: Alas, `Double.isFinite(d)` only added in JDK 8 double d = _numberDouble; return Double.isNaN(d) || Double.isInfinite(d); } } return false; } /* /********************************************************** /* Numeric accessors of public API /********************************************************** */ @Override public Number getNumberValue() throws IOException { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_UNKNOWN); // will also check event type } // Separate types for int types if (_currToken == JsonToken.VALUE_NUMBER_INT) { if ((_numTypesValid & NR_INT) != 0) { return _numberInt; } if ((_numTypesValid & NR_LONG) != 0) { return _numberLong; } if ((_numTypesValid & NR_BIGINT) != 0) { return _numberBigInt; } _throwInternal(); } // And then floating point types. But here optimal type // needs to be big decimal, to avoid losing any data? if ((_numTypesValid & NR_BIGDECIMAL) != 0) { return _numberBigDecimal; } if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check _throwInternal(); } return _numberDouble; } // NOTE: mostly copied from above @Override public Number getNumberValueExact() throws IOException { if (_currToken == JsonToken.VALUE_NUMBER_INT) { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_UNKNOWN); } if ((_numTypesValid & NR_INT) != 0) { return _numberInt; } if ((_numTypesValid & NR_LONG) != 0) { return _numberLong; } if ((_numTypesValid & NR_BIGINT) != 0) { return _numberBigInt; } _throwInternal(); } // 09-Jul-2020, tatu: [databind#2644] requires we will retain accuracy, so: if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_BIGDECIMAL); } if ((_numTypesValid & NR_BIGDECIMAL) != 0) { return _numberBigDecimal; } if ((_numTypesValid & NR_DOUBLE) == 0) { // sanity check _throwInternal(); } return _numberDouble; } @Override public NumberType getNumberType() throws IOException { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_UNKNOWN); // will also check event type } if (_currToken == JsonToken.VALUE_NUMBER_INT) { if ((_numTypesValid & NR_INT) != 0) { return NumberType.INT; } if ((_numTypesValid & NR_LONG) != 0) { return NumberType.LONG; } return NumberType.BIG_INTEGER; } /* And then floating point types. Here optimal type * needs to be big decimal, to avoid losing any data? * However... using BD is slow, so let's allow returning * double as type if no explicit call has been made to access * data as BD? */ if ((_numTypesValid & NR_BIGDECIMAL) != 0) { return NumberType.BIG_DECIMAL; } return NumberType.DOUBLE; } @Override public int getIntValue() throws IOException { if ((_numTypesValid & NR_INT) == 0) { if (_numTypesValid == NR_UNKNOWN) { // not parsed at all return _parseIntValue(); } if ((_numTypesValid & NR_INT) == 0) { // wasn't an int natively? convertNumberToInt(); // let's make it so, if possible } } return _numberInt; } @Override public long getLongValue() throws IOException { if ((_numTypesValid & NR_LONG) == 0) { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_LONG); } if ((_numTypesValid & NR_LONG) == 0) { convertNumberToLong(); } } return _numberLong; } @Override public BigInteger getBigIntegerValue() throws IOException { if ((_numTypesValid & NR_BIGINT) == 0) { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_BIGINT); } if ((_numTypesValid & NR_BIGINT) == 0) { convertNumberToBigInteger(); } } return _numberBigInt; } @Override public float getFloatValue() throws IOException { double value = getDoubleValue(); /* 22-Jan-2009, tatu: Bounds/range checks would be tricky * here, so let's not bother even trying... */ /* if (value < -Float.MAX_VALUE || value > MAX_FLOAT_D) { _reportError("Numeric value ("+getText()+") out of range of Java float"); } */ return (float) value; } @Override public double getDoubleValue() throws IOException { if ((_numTypesValid & NR_DOUBLE) == 0) { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_DOUBLE); } if ((_numTypesValid & NR_DOUBLE) == 0) { convertNumberToDouble(); } } return _numberDouble; } @Override public BigDecimal getDecimalValue() throws IOException { if ((_numTypesValid & NR_BIGDECIMAL) == 0) { if (_numTypesValid == NR_UNKNOWN) { _parseNumericValue(NR_BIGDECIMAL); } if ((_numTypesValid & NR_BIGDECIMAL) == 0) { convertNumberToBigDecimal(); } } return _numberBigDecimal; } /* /********************************************************** /* Conversion from textual to numeric representation /********************************************************** */
Method that will parse actual numeric value out of a syntactically valid number value. Type it will parse into depends on whether it is a floating point number, as well as its magnitude: smallest legal type (of ones available) is used for efficiency.
Params:
  • expType – Numeric type that we will immediately need, if any; mostly necessary to optimize handling of floating point numbers
/** * Method that will parse actual numeric value out of a syntactically * valid number value. Type it will parse into depends on whether * it is a floating point number, as well as its magnitude: smallest * legal type (of ones available) is used for efficiency. * * @param expType Numeric type that we will immediately need, if any; * mostly necessary to optimize handling of floating point numbers */
protected void _parseNumericValue(int expType) throws IOException { // 12-Jun-2020, tatu: Sanity check to prevent more cryptic error for this case. // (note: could alternatively see if TextBuffer has aggregated contents, avoid // exception -- but that might be more confusing) if (_closed) { _reportError("Internal error: _parseNumericValue called when parser instance closed"); } // Int or float? if (_currToken == JsonToken.VALUE_NUMBER_INT) { final int len = _intLength; // First: optimization for simple int if (len <= 9) { int i = _textBuffer.contentsAsInt(_numberNegative); _numberInt = i; _numTypesValid = NR_INT; return; } if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls long l = _textBuffer.contentsAsLong(_numberNegative); // Might still fit in int, need to check if (len == 10) { if (_numberNegative) { if (l >= MIN_INT_L) { _numberInt = (int) l; _numTypesValid = NR_INT; return; } } else { if (l <= MAX_INT_L) { _numberInt = (int) l; _numTypesValid = NR_INT; return; } } } _numberLong = l; _numTypesValid = NR_LONG; return; } _parseSlowInt(expType); return; } if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) { _parseSlowFloat(expType); return; } _reportError("Current token (%s) not numeric, can not use numeric value accessors", _currToken); }
Since:2.6
/** * @since 2.6 */
protected int _parseIntValue() throws IOException { // 12-Jun-2020, tatu: Sanity check to prevent more cryptic error for this case. // (note: could alternatively see if TextBuffer has aggregated contents, avoid // exception -- but that might be more confusing) if (_closed) { _reportError("Internal error: _parseNumericValue called when parser instance closed"); } // Inlined variant of: _parseNumericValue(NR_INT) if (_currToken == JsonToken.VALUE_NUMBER_INT) { if (_intLength <= 9) { int i = _textBuffer.contentsAsInt(_numberNegative); _numberInt = i; _numTypesValid = NR_INT; return i; } } // if not optimizable, use more generic _parseNumericValue(NR_INT); if ((_numTypesValid & NR_INT) == 0) { convertNumberToInt(); } return _numberInt; } private void _parseSlowFloat(int expType) throws IOException { /* Nope: floating point. Here we need to be careful to get * optimal parsing strategy: choice is between accurate but * slow (BigDecimal) and lossy but fast (Double). For now * let's only use BD when explicitly requested -- it can * still be constructed correctly at any point since we do * retain textual representation */ try { if (expType == NR_BIGDECIMAL) { _numberBigDecimal = _textBuffer.contentsAsDecimal(); _numTypesValid = NR_BIGDECIMAL; } else { // Otherwise double has to do _numberDouble = _textBuffer.contentsAsDouble(); _numTypesValid = NR_DOUBLE; } } catch (NumberFormatException nex) { // Can this ever occur? Due to overflow, maybe? _wrapError("Malformed numeric value ("+_longNumberDesc(_textBuffer.contentsAsString())+")", nex); } } private void _parseSlowInt(int expType) throws IOException { String numStr = _textBuffer.contentsAsString(); try { int len = _intLength; char[] buf = _textBuffer.getTextBuffer(); int offset = _textBuffer.getTextOffset(); if (_numberNegative) { ++offset; } // Some long cases still... if (NumberInput.inLongRange(buf, offset, len, _numberNegative)) { // Probably faster to construct a String, call parse, than to use BigInteger _numberLong = Long.parseLong(numStr); _numTypesValid = NR_LONG; } else { // 16-Oct-2018, tatu: Need to catch "too big" early due to [jackson-core#488] if ((expType == NR_INT) || (expType == NR_LONG)) { _reportTooLongIntegral(expType, numStr); } if ((expType == NR_DOUBLE) || (expType == NR_FLOAT)) { _numberDouble = NumberInput.parseDouble(numStr); _numTypesValid = NR_DOUBLE; } else { // nope, need the heavy guns... (rare case) _numberBigInt = new BigInteger(numStr); _numTypesValid = NR_BIGINT; } } } catch (NumberFormatException nex) { // Can this ever occur? Due to overflow, maybe? _wrapError("Malformed numeric value ("+_longNumberDesc(numStr)+")", nex); } } // @since 2.9.8 protected void _reportTooLongIntegral(int expType, String rawNum) throws IOException { if (expType == NR_INT) { reportOverflowInt(rawNum); } else { reportOverflowLong(rawNum); } } /* /********************************************************** /* Numeric conversions /********************************************************** */ protected void convertNumberToInt() throws IOException { // First, converting from long ought to be easy if ((_numTypesValid & NR_LONG) != 0) { // Let's verify it's lossless conversion by simple roundtrip int result = (int) _numberLong; if (((long) result) != _numberLong) { reportOverflowInt(getText(), currentToken()); } _numberInt = result; } else if ((_numTypesValid & NR_BIGINT) != 0) { if (BI_MIN_INT.compareTo(_numberBigInt) > 0 || BI_MAX_INT.compareTo(_numberBigInt) < 0) { reportOverflowInt(); } _numberInt = _numberBigInt.intValue(); } else if ((_numTypesValid & NR_DOUBLE) != 0) { // Need to check boundaries if (_numberDouble < MIN_INT_D || _numberDouble > MAX_INT_D) { reportOverflowInt(); } _numberInt = (int) _numberDouble; } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) { if (BD_MIN_INT.compareTo(_numberBigDecimal) > 0 || BD_MAX_INT.compareTo(_numberBigDecimal) < 0) { reportOverflowInt(); } _numberInt = _numberBigDecimal.intValue(); } else { _throwInternal(); } _numTypesValid |= NR_INT; } protected void convertNumberToLong() throws IOException { if ((_numTypesValid & NR_INT) != 0) { _numberLong = (long) _numberInt; } else if ((_numTypesValid & NR_BIGINT) != 0) { if (BI_MIN_LONG.compareTo(_numberBigInt) > 0 || BI_MAX_LONG.compareTo(_numberBigInt) < 0) { reportOverflowLong(); } _numberLong = _numberBigInt.longValue(); } else if ((_numTypesValid & NR_DOUBLE) != 0) { // Need to check boundaries if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) { reportOverflowLong(); } _numberLong = (long) _numberDouble; } else if ((_numTypesValid & NR_BIGDECIMAL) != 0) { if (BD_MIN_LONG.compareTo(_numberBigDecimal) > 0 || BD_MAX_LONG.compareTo(_numberBigDecimal) < 0) { reportOverflowLong(); } _numberLong = _numberBigDecimal.longValue(); } else { _throwInternal(); } _numTypesValid |= NR_LONG; } protected void convertNumberToBigInteger() throws IOException { if ((_numTypesValid & NR_BIGDECIMAL) != 0) { // here it'll just get truncated, no exceptions thrown _numberBigInt = _numberBigDecimal.toBigInteger(); } else if ((_numTypesValid & NR_LONG) != 0) { _numberBigInt = BigInteger.valueOf(_numberLong); } else if ((_numTypesValid & NR_INT) != 0) { _numberBigInt = BigInteger.valueOf(_numberInt); } else if ((_numTypesValid & NR_DOUBLE) != 0) { _numberBigInt = BigDecimal.valueOf(_numberDouble).toBigInteger(); } else { _throwInternal(); } _numTypesValid |= NR_BIGINT; } protected void convertNumberToDouble() throws IOException { /* 05-Aug-2008, tatus: Important note: this MUST start with * more accurate representations, since we don't know which * value is the original one (others get generated when * requested) */ if ((_numTypesValid & NR_BIGDECIMAL) != 0) { _numberDouble = _numberBigDecimal.doubleValue(); } else if ((_numTypesValid & NR_BIGINT) != 0) { _numberDouble = _numberBigInt.doubleValue(); } else if ((_numTypesValid & NR_LONG) != 0) { _numberDouble = (double) _numberLong; } else if ((_numTypesValid & NR_INT) != 0) { _numberDouble = (double) _numberInt; } else { _throwInternal(); } _numTypesValid |= NR_DOUBLE; } protected void convertNumberToBigDecimal() throws IOException { /* 05-Aug-2008, tatus: Important note: this MUST start with * more accurate representations, since we don't know which * value is the original one (others get generated when * requested) */ if ((_numTypesValid & NR_DOUBLE) != 0) { /* Let's actually parse from String representation, to avoid * rounding errors that non-decimal floating operations could incur */ _numberBigDecimal = NumberInput.parseBigDecimal(getText()); } else if ((_numTypesValid & NR_BIGINT) != 0) { _numberBigDecimal = new BigDecimal(_numberBigInt); } else if ((_numTypesValid & NR_LONG) != 0) { _numberBigDecimal = BigDecimal.valueOf(_numberLong); } else if ((_numTypesValid & NR_INT) != 0) { _numberBigDecimal = BigDecimal.valueOf(_numberInt); } else { _throwInternal(); } _numTypesValid |= NR_BIGDECIMAL; } /* /********************************************************** /* Internal/package methods: Error reporting /********************************************************** */ protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException { JsonReadContext ctxt = getParsingContext(); _reportError(String.format( "Unexpected close marker '%s': expected '%c' (for %s starting at %s)", (char) actCh, expCh, ctxt.typeDesc(), ctxt.getStartLocation(_getSourceReference()))); } @SuppressWarnings("deprecation") protected char _handleUnrecognizedCharacterEscape(char ch) throws JsonProcessingException { // as per [JACKSON-300] if (isEnabled(Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER)) { return ch; } // and [JACKSON-548] if (ch == '\'' && isEnabled(Feature.ALLOW_SINGLE_QUOTES)) { return ch; } _reportError("Unrecognized character escape "+_getCharDesc(ch)); return ch; }
Method called to report a problem with unquoted control character. Note: it is possible to suppress some instances of exception by enabling JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.
/** * Method called to report a problem with unquoted control character. * Note: it is possible to suppress some instances of * exception by enabling * {@link com.fasterxml.jackson.core.json.JsonReadFeature#ALLOW_UNESCAPED_CONTROL_CHARS}. */
@SuppressWarnings("deprecation") protected void _throwUnquotedSpace(int i, String ctxtDesc) throws JsonParseException { // JACKSON-208; possible to allow unquoted control chars: if (!isEnabled(Feature.ALLOW_UNQUOTED_CONTROL_CHARS) || i > INT_SPACE) { char c = (char) i; String msg = "Illegal unquoted character ("+_getCharDesc(c)+"): has to be escaped using backslash to be included in "+ctxtDesc; _reportError(msg); } }
Returns:Description to use as "valid tokens" in an exception message about invalid (unrecognized) JSON token: called when parser finds something that looks like unquoted textual token
Since:2.10
/** * @return Description to use as "valid tokens" in an exception message about * invalid (unrecognized) JSON token: called when parser finds something that * looks like unquoted textual token * * @since 2.10 */
protected String _validJsonTokenList() throws IOException { return _validJsonValueList(); }
Returns:Description to use as "valid JSON values" in an exception message about invalid (unrecognized) JSON value: called when parser finds something that does not look like a value or separator.
Since:2.10
/** * @return Description to use as "valid JSON values" in an exception message about * invalid (unrecognized) JSON value: called when parser finds something that * does not look like a value or separator. * * @since 2.10 */
@SuppressWarnings("deprecation") protected String _validJsonValueList() throws IOException { if (isEnabled(Feature.ALLOW_NON_NUMERIC_NUMBERS)) { return "(JSON String, Number (or 'NaN'/'INF'/'+INF'), Array, Object or token 'null', 'true' or 'false')"; } return "(JSON String, Number, Array, Object or token 'null', 'true' or 'false')"; } /* /********************************************************** /* Base64 handling support /********************************************************** */
Method that sub-classes must implement to support escaped sequences in base64-encoded sections. Sub-classes that do not need base64 support can leave this as is
/** * Method that sub-classes must implement to support escaped sequences * in base64-encoded sections. * Sub-classes that do not need base64 support can leave this as is */
protected char _decodeEscaped() throws IOException { throw new UnsupportedOperationException(); } protected final int _decodeBase64Escape(Base64Variant b64variant, int ch, int index) throws IOException { // 17-May-2011, tatu: As per [JACKSON-xxx], need to handle escaped chars if (ch != '\\') { throw reportInvalidBase64Char(b64variant, ch, index); } int unescaped = _decodeEscaped(); // if white space, skip if first triplet; otherwise errors if (unescaped <= INT_SPACE) { if (index == 0) { // whitespace only allowed to be skipped between triplets return -1; } } // otherwise try to find actual triplet value int bits = b64variant.decodeBase64Char(unescaped); if (bits < 0) { if (bits != Base64Variant.BASE64_VALUE_PADDING) { throw reportInvalidBase64Char(b64variant, unescaped, index); } } return bits; } protected final int _decodeBase64Escape(Base64Variant b64variant, char ch, int index) throws IOException { if (ch != '\\') { throw reportInvalidBase64Char(b64variant, ch, index); } char unescaped = _decodeEscaped(); // if white space, skip if first triplet; otherwise errors if (unescaped <= INT_SPACE) { if (index == 0) { // whitespace only allowed to be skipped between triplets return -1; } } // otherwise try to find actual triplet value int bits = b64variant.decodeBase64Char(unescaped); if (bits < 0) { // second check since padding can only be 3rd or 4th byte (index #2 or #3) if ((bits != Base64Variant.BASE64_VALUE_PADDING) || (index < 2)) { throw reportInvalidBase64Char(b64variant, unescaped, index); } } return bits; } protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex) throws IllegalArgumentException { return reportInvalidBase64Char(b64variant, ch, bindex, null); }
Params:
  • bindex – Relative index within base64 character unit; between 0 and 3 (as unit has exactly 4 characters)
/** * @param bindex Relative index within base64 character unit; between 0 * and 3 (as unit has exactly 4 characters) */
protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64variant, int ch, int bindex, String msg) throws IllegalArgumentException { String base; if (ch <= INT_SPACE) { base = String.format("Illegal white space character (code 0x%s) as character #%d of 4-char base64 unit: can only used between units", Integer.toHexString(ch), (bindex+1)); } else if (b64variant.usesPaddingChar(ch)) { base = "Unexpected padding character ('"+b64variant.getPaddingChar()+"') as character #"+(bindex+1)+" of 4-char base64 unit: padding only legal as 3rd or 4th character"; } else if (!Character.isDefined(ch) || Character.isISOControl(ch)) { // Not sure if we can really get here... ? (most illegal xml chars are caught at lower level) base = "Illegal character (code 0x"+Integer.toHexString(ch)+") in base64 content"; } else { base = "Illegal character '"+((char)ch)+"' (code 0x"+Integer.toHexString(ch)+") in base64 content"; } if (msg != null) { base = base + ": " + msg; } return new IllegalArgumentException(base); } // since 2.9.8 protected void _handleBase64MissingPadding(Base64Variant b64variant) throws IOException { _reportError(b64variant.missingPaddingMessage()); } /* /********************************************************** /* Internal/package methods: other /********************************************************** */
Helper method used to encapsulate logic of including (or not) of "source reference" when constructing JsonLocation instances.
Since:2.9
/** * Helper method used to encapsulate logic of including (or not) of * "source reference" when constructing {@link JsonLocation} instances. * * @since 2.9 */
protected Object _getSourceReference() { if (JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION.enabledIn(_features)) { return _ioContext.getSourceReference(); } return null; } protected static int[] growArrayBy(int[] arr, int more) { if (arr == null) { return new int[more]; } return Arrays.copyOf(arr, arr.length + more); } /* /********************************************************** /* Stuff that was abstract and required before 2.8, but that /* is not mandatory in 2.8 or above. /********************************************************** */ @Deprecated // since 2.8 protected void loadMoreGuaranteed() throws IOException { if (!loadMore()) { _reportInvalidEOF(); } } @Deprecated // since 2.8 protected boolean loadMore() throws IOException { return false; } // Can't declare as deprecated, for now, but shouldn't be needed protected void _finishString() throws IOException { } }