| 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.JacksonFeatureSet; |
| import com.fasterxml.jackson.core.util.TextBuffer; |
| |
| /** |
| * 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. |
| */ |
| final protected 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; |
| |
| /* |
| /********************************************************** |
| /* Current input data |
| /********************************************************** |
| */ |
| |
| // Note: type of actual buffer depends on sub-class, can't include |
| |
| /** |
| * Pointer to next available character in buffer |
| */ |
| protected int _inputPtr; |
| |
| /** |
| * 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). |
| */ |
| protected long _currInputProcessed; |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| protected long _tokenInputTotal; |
| |
| /** |
| * 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) |
| */ |
| protected int _tokenInputCol; |
| |
| /* |
| /********************************************************** |
| /* Parsing state |
| /********************************************************** |
| */ |
| |
| /** |
| * 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. |
| */ |
| 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) |
| */ |
| protected final TextBuffer _textBuffer; |
| |
| /** |
| * 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) |
| */ |
| protected boolean _nameCopied; |
| |
| /** |
| * 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 |
| * {@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 |
| */ |
| 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. |
| */ |
| protected boolean _numberNegative; |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| 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. |
| * |
| * @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. |
| */ |
| @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 <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 |
| */ |
| @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 {@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. |
| */ |
| @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 |
| */ |
| 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. |
| * |
| * @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 |
| */ |
| 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 {@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); |
| } |
| } |
| |
| /** |
| * @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(); |
| } |
| |
| /** |
| * @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 |
| */ |
| 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); |
| } |
| |
| /** |
| * @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 {@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 { } |
| } |