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.JsonParser.Feature;
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.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
{
/*
/**********************************************************
/* 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;
}
// Shouldn't get this far but if we do
return _numberBigDecimal;
}
/* 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;
}
@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
{
// Int or float?
if (_currToken == JsonToken.VALUE_NUMBER_INT) {
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
{
// 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 Feature.ALLOW_UNQUOTED_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 Feature#ALLOW_UNQUOTED_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 { }
}