| package com.fasterxml.jackson.core.base; |
| |
| import java.io.*; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| |
| 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.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 |
| { |
| /* |
| /********************************************************** |
| /* 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 = 0; |
| |
| /** |
| * Index of character after last available one in the buffer. |
| */ |
| protected int _inputEnd = 0; |
| |
| /* |
| /********************************************************** |
| /* 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 = 0L; |
| |
| /** |
| * 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 = 0; |
| |
| /* |
| /********************************************************** |
| /* 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 = 0; |
| |
| /** |
| * 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 = 0; |
| |
| /* |
| /********************************************************** |
| /* 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 = null; |
| |
| /** |
| * 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 = false; |
| |
| /** |
| * ByteArrayBuilder is needed if 'getBinaryValue' is called. If so, |
| * we better reuse it for remainder of content. |
| */ |
| protected ByteArrayBuilder _byteArrayBuilder = null; |
| |
| /** |
| * 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; |
| |
| /* |
| /********************************************************** |
| /* Constants and fields of former 'JsonNumericParserBase' |
| /********************************************************** |
| */ |
| |
| final protected static int NR_UNKNOWN = 0; |
| |
| // First, integer types |
| |
| final protected static int NR_INT = 0x0001; |
| final protected static int NR_LONG = 0x0002; |
| final protected static int NR_BIGINT = 0x0004; |
| |
| // And then floating point types |
| |
| final protected static int NR_DOUBLE = 0x008; |
| final protected static int NR_BIGDECIMAL = 0x0010; |
| |
| // Also, we need some numeric constants |
| |
| final static BigInteger BI_MIN_INT = BigInteger.valueOf(Integer.MIN_VALUE); |
| final static BigInteger BI_MAX_INT = BigInteger.valueOf(Integer.MAX_VALUE); |
| |
| final static BigInteger BI_MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); |
| final static BigInteger BI_MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); |
| |
| final static BigDecimal BD_MIN_LONG = new BigDecimal(BI_MIN_LONG); |
| final static BigDecimal BD_MAX_LONG = new BigDecimal(BI_MAX_LONG); |
| |
| final static BigDecimal BD_MIN_INT = new BigDecimal(BI_MIN_INT); |
| final static BigDecimal BD_MAX_INT = new BigDecimal(BI_MAX_INT); |
| |
| final static long MIN_INT_L = (long) Integer.MIN_VALUE; |
| final static long MAX_INT_L = (long) Integer.MAX_VALUE; |
| |
| // These are not very accurate, but have to do... (for bounds checks) |
| |
| final static double MIN_LONG_D = (double) Long.MIN_VALUE; |
| final static double MAX_LONG_D = (double) Long.MAX_VALUE; |
| |
| final static double MIN_INT_D = (double) Integer.MIN_VALUE; |
| final static double MAX_INT_D = (double) Integer.MAX_VALUE; |
| |
| // Digits, numeric |
| final protected static int INT_0 = '0'; |
| final protected static int INT_9 = '9'; |
| |
| final protected static int INT_MINUS = '-'; |
| final protected static int INT_PLUS = '+'; |
| |
| final protected static char CHAR_NULL = '\0'; |
| |
| // 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 |
| public JsonParser setFeatureMask(int newMask) { |
| int changes = (_features ^ newMask); |
| if (changes != 0) { |
| _features = newMask; |
| if (Feature.STRICT_DUPLICATE_DETECTION.enabledIn(newMask)) { // enabling |
| if (_parsingContext.getDupDetector() == null) { // but only if disabled currently |
| _parsingContext = _parsingContext.withDupDetector(DupDetector.rootDetector(this)); |
| } |
| } else { // disabling |
| _parsingContext = _parsingContext.withDupDetector(null); |
| } |
| } |
| return this; |
| } |
| |
| /* |
| /********************************************************** |
| /* 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(); |
| 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) { |
| _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(_ioContext.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(_ioContext.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; |
| } |
| |
| // No embedded objects with base impl... |
| @Override public Object getEmbeddedObject() throws IOException { return null; } |
| |
| /* |
| /********************************************************** |
| /* 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); |
| } |
| |
| /* |
| /********************************************************** |
| /* Low-level reading, other |
| /********************************************************** |
| */ |
| |
| protected final void loadMoreGuaranteed() throws IOException { |
| if (!loadMore()) { _reportInvalidEOF(); } |
| } |
| |
| /* |
| /********************************************************** |
| /* Abstract methods needed from sub-classes |
| /********************************************************** |
| */ |
| |
| protected abstract boolean loadMore() throws IOException; |
| protected abstract void _finishString() throws IOException; |
| 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()) { |
| _reportInvalidEOF(": expected close marker for "+_parsingContext.getTypeDesc()+" (from "+_parsingContext.getStartLocation(_ioContext.getSourceReference())+")"); |
| } |
| } |
| |
| /** |
| * @since 2.4 |
| */ |
| protected final int _eofAsNextChar() throws JsonParseException { |
| _handleEOF(); |
| return -1; |
| } |
| |
| /* |
| /********************************************************** |
| /* Internal/package methods: Error reporting |
| /********************************************************** |
| */ |
| |
| protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException { |
| String startDesc = ""+_parsingContext.getStartLocation(_ioContext.getSourceReference()); |
| _reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.getTypeDesc()+" starting at "+startDesc+")"); |
| } |
| |
| /* |
| /********************************************************** |
| /* 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; |
| } |
| |
| /* |
| /********************************************************** |
| /* 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 |
| { |
| // Int or float? |
| if (_currToken == JsonToken.VALUE_NUMBER_INT) { |
| char[] buf = _textBuffer.getTextBuffer(); |
| int offset = _textBuffer.getTextOffset(); |
| int len = _intLength; |
| if (_numberNegative) { |
| ++offset; |
| } |
| if (len <= 9) { // definitely fits in int |
| int i = NumberInput.parseInt(buf, offset, len); |
| _numberInt = _numberNegative ? -i : i; |
| _numTypesValid = NR_INT; |
| return; |
| } |
| if (len <= 18) { // definitely fits AND is easy to parse using 2 int parse calls |
| long l = NumberInput.parseLong(buf, offset, len); |
| if (_numberNegative) { |
| l = -l; |
| } |
| // [JACKSON-230] Could 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, buf, offset, len); |
| return; |
| } |
| if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) { |
| _parseSlowFloat(expType); |
| return; |
| } |
| _reportError("Current token ("+_currToken+") not numeric, can not use numeric value accessors"); |
| } |
| |
| /** |
| * @since 2.6 |
| */ |
| protected int _parseIntValue() throws IOException |
| { |
| // Inlined variant of: _parseNumericValue(NR_INT) |
| |
| if (_currToken == JsonToken.VALUE_NUMBER_INT) { |
| char[] buf = _textBuffer.getTextBuffer(); |
| int offset = _textBuffer.getTextOffset(); |
| int len = _intLength; |
| if (_numberNegative) { |
| ++offset; |
| } |
| if (len <= 9) { |
| int i = NumberInput.parseInt(buf, offset, len); |
| if (_numberNegative) { |
| i = -i; |
| } |
| _numberInt = i; |
| _numTypesValid = NR_INT; |
| return i; |
| } |
| } |
| _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 '"+_textBuffer.contentsAsString()+"'", nex); |
| } |
| } |
| |
| private void _parseSlowInt(int expType, char[] buf, int offset, int len) throws IOException |
| { |
| String numStr = _textBuffer.contentsAsString(); |
| try { |
| // [JACKSON-230] 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 { |
| // 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 '"+numStr+"'", nex); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* 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) { |
| _reportError("Numeric value ("+getText()+") out of range of int"); |
| } |
| _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 |
| * would 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; |
| } |
| |
| /* |
| /********************************************************** |
| /* Number handling exceptions |
| /********************************************************** |
| */ |
| |
| protected void reportUnexpectedNumberChar(int ch, String comment) throws JsonParseException { |
| String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value"; |
| if (comment != null) { |
| msg += ": "+comment; |
| } |
| _reportError(msg); |
| } |
| |
| protected void reportInvalidNumber(String msg) throws JsonParseException { |
| _reportError("Invalid numeric value: "+msg); |
| } |
| |
| protected void reportOverflowInt() throws IOException { |
| _reportError("Numeric value ("+getText()+") out of range of int ("+Integer.MIN_VALUE+" - "+Integer.MAX_VALUE+")"); |
| } |
| |
| protected void reportOverflowLong() throws IOException { |
| _reportError("Numeric value ("+getText()+") out of range of long ("+Long.MIN_VALUE+" - "+Long.MAX_VALUE+")"); |
| } |
| |
| /* |
| /********************************************************** |
| /* 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) { |
| throw reportInvalidBase64Char(b64variant, unescaped, index); |
| } |
| return bits; |
| } |
| |
| protected final int _decodeBase64Escape(Base64Variant b64variant, char 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); |
| } |
| 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) { |
| 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 = "Illegal white space character (code 0x"+Integer.toHexString(ch)+") as character #"+(bindex+1)+" of 4-char base64 unit: can only used between units"; |
| } 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); |
| } |
| } |