Java tutorial
package com.fasterxml.jackson.dataformat.cbor; import java.io.*; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.Arrays; import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.base.ParserMinimalBase; 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.sym.ByteQuadsCanonicalizer; import com.fasterxml.jackson.core.sym.FieldNameMatcher; import com.fasterxml.jackson.core.util.ByteArrayBuilder; import com.fasterxml.jackson.core.util.TextBuffer; import static com.fasterxml.jackson.dataformat.cbor.CBORConstants.*; public class CBORParser extends ParserMinimalBase { /** * Enumeration that defines all togglable features for CBOR generators. */ public enum Feature implements FormatFeature { // BOGUS(false) ; final boolean _defaultState; final int _mask; /** * Method that calculates bit set (flags) of all features that * are enabled by default. */ public static int collectDefaults() { int flags = 0; for (Feature f : values()) { if (f.enabledByDefault()) { flags |= f.getMask(); } } return flags; } private Feature(boolean defaultState) { _defaultState = defaultState; _mask = (1 << ordinal()); } @Override public boolean enabledByDefault() { return _defaultState; } @Override public int getMask() { return _mask; } @Override public boolean enabledIn(int flags) { return (flags & _mask) != 0; } } private final static Charset UTF8 = Charset.forName("UTF-8"); private final static int[] UTF8_UNIT_CODES = CBORConstants.sUtf8UnitLengths; // Constants for handling of 16-bit "mini-floats" private final static double MATH_POW_2_10 = Math.pow(2, 10); private final static double MATH_POW_2_NEG14 = Math.pow(2, -14); /* /********************************************************** /* 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 CBORReadContext _parsingContext; /** * 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; /** * We will keep track of tag value for possible future use. */ protected int _tagValue = -1; /* /********************************************************** /* Input source config, state (from ex StreamBasedParserBase) /********************************************************** */ /** * Input stream that can be used for reading more content, if one * in use. May be null, if input comes just as a full buffer, * or if the stream has been closed. */ protected InputStream _inputStream; /** * Current buffer from which data is read; generally data is read into * buffer from input source, but in some cases pre-loaded buffer * is handed to the parser. */ protected byte[] _inputBuffer; /** * Flag that indicates whether the input buffer is recycable (and * needs to be returned to recycler once we are done) or not. *<p> * If it is not, it also means that parser can NOT modify underlying * buffer. */ protected boolean _bufferRecyclable; /* /********************************************************** /* Additional parsing state /********************************************************** */ /** * Flag that indicates that the current token has not yet * been fully processed, and needs to be finished for * some access (or skipped to obtain the next token) */ protected boolean _tokenIncomplete = false; /** * Type byte of the current token */ protected int _typeByte; /** * Helper variables used when dealing with chunked content. */ private int _chunkLeft, _chunkEnd; /* /********************************************************** /* Symbol handling, decoding /********************************************************** */ /** * Symbol table that contains field names encountered so far */ final protected ByteQuadsCanonicalizer _symbols; /** * Temporary buffer used for name parsing. */ protected int[] _quadBuffer = NO_INTS; /** * Quads used for hash calculation */ protected int _quad1, _quad2, _quad3; /* /********************************************************** /* Constants and fields of former 'JsonNumericParserBase' /********************************************************** */ // 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 float _numberFloat; protected double _numberDouble; // And then object types protected BigInteger _numberBigInt; protected BigDecimal _numberBigDecimal; /* /********************************************************** /* Life-cycle /********************************************************** */ public CBORParser(ObjectReadContext readCtxt, IOContext ioCtxt, int parserFeatures, int cborFeatures, ByteQuadsCanonicalizer sym, InputStream in, byte[] inputBuffer, int start, int end, boolean bufferRecyclable) { super(readCtxt, parserFeatures); _ioContext = ioCtxt; _symbols = sym; _inputStream = in; _inputBuffer = inputBuffer; _inputPtr = start; _inputEnd = end; _bufferRecyclable = bufferRecyclable; _textBuffer = ioCtxt.constructTextBuffer(); DupDetector dups = StreamReadFeature.STRICT_DUPLICATE_DETECTION.enabledIn(parserFeatures) ? DupDetector.rootDetector(this) : null; _parsingContext = CBORReadContext.createRootContext(dups); _tokenInputRow = -1; _tokenInputCol = -1; } /* /********************************************************** /* Versioned /********************************************************** */ @Override public Version version() { return PackageVersion.VERSION; } /* /********************************************************** /* Configuration /********************************************************** */ /* /********************************************************** /* Extended API /********************************************************** */ /** * Method that can be used to access tag id associated with * the most recently decoded value (whether completely, for * scalar values, or partially, for Objects/Arrays), if any. * If no tag was associated with it, -1 is returned. */ public int getCurrentTag() { return _tagValue; } /* /********************************************************** /* Abstract impls /********************************************************** */ @Override public int releaseBuffered(OutputStream out) throws IOException { int count = _inputEnd - _inputPtr; if (count < 1) { return 0; } // let's just advance ptr to end int origPtr = _inputPtr; out.write(_inputBuffer, origPtr, count); return count; } @Override public Object getInputSource() { return _inputStream; } /** * Overridden since we do not really have character-based locations, * but we do have byte offset to specify. */ @Override public JsonLocation getTokenLocation() { // token location is correctly managed... return new JsonLocation(_ioContext.getSourceReference(), _tokenInputTotal, // bytes -1, -1, (int) _tokenInputTotal); // char offset, line, column } /** * Overridden since we do not really have character-based locations, * but we do have byte offset to specify. */ @Override public JsonLocation getCurrentLocation() { final long offset = _currInputProcessed + _inputPtr; return new JsonLocation(_ioContext.getSourceReference(), offset, // bytes -1, -1, (int) offset); // char offset, line, column } /** * Method that can be called to get the name associated with * the current event. */ @Override public String currentName() throws IOException { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { CBORReadContext parent = _parsingContext.getParent(); return parent.currentName(); } return _parsingContext.currentName(); } @Override public void overrideCurrentName(String name) { // Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing: CBORReadContext ctxt = _parsingContext; if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { ctxt = ctxt.getParent(); } // Unfortunate, but since we did not expose exceptions, need to wrap try { ctxt.setCurrentName(name); } catch (IOException e) { throw new IllegalStateException(e); } } @Override public void close() throws IOException { if (!_closed) { _closed = true; _symbols.release(); 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 CBORReadContext getParsingContext() { return _parsingContext; } /* /********************************************************** /* Overridden methods /********************************************************** */ @Override public boolean hasTextCharacters() { if (_currToken == JsonToken.VALUE_STRING) { // yes; is or can be made available efficiently as char[] return _textBuffer.hasTextAsCharacters(); } if (_currToken == JsonToken.FIELD_NAME) { // not necessarily; possible but: return _nameCopied; } // other types, no benefit from accessing as char[] return false; } /** * 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 { if (_bufferRecyclable) { byte[] buf = _inputBuffer; if (buf != null) { _inputBuffer = null; _ioContext.releaseReadIOBuffer(buf); } } _textBuffer.releaseBuffers(); char[] buf = _nameCopyBuffer; if (buf != null) { _nameCopyBuffer = null; _ioContext.releaseNameCopyBuffer(buf); } } /* /********************************************************** /* JsonParser impl /********************************************************** */ @Override public JsonToken nextToken() throws IOException { _numTypesValid = NR_UNKNOWN; // For longer tokens (text, binary), we'll only read when requested if (_tokenIncomplete) { _skipIncomplete(); } _tokenInputTotal = _currInputProcessed + _inputPtr; // also: clear any data retained so far _binaryValue = null; /* First: need to keep track of lengths of defined-length Arrays and * Objects (to materialize END_ARRAY/END_OBJECT as necessary); * as well as handle names for Object entries. */ if (_parsingContext.inObject()) { if (_currToken != JsonToken.FIELD_NAME) { _tagValue = -1; // completed the whole Object? if (!_parsingContext.expectMoreValues()) { _parsingContext = _parsingContext.getParent(); return (_currToken = JsonToken.END_OBJECT); } return (_currToken = _decodeFieldName()); } } else { if (!_parsingContext.expectMoreValues()) { _tagValue = -1; _parsingContext = _parsingContext.getParent(); return (_currToken = JsonToken.END_ARRAY); } } if (_inputPtr >= _inputEnd) { if (!loadMore()) { return _handleCBOREOF(); } } int ch = _inputBuffer[_inputPtr++]; int type = (ch >> 5) & 0x7; // One special case: need to consider tag as prefix first: if (type == 6) { _tagValue = Integer.valueOf(_decodeTag(ch & 0x1F)); if (_inputPtr >= _inputEnd) { if (!loadMore()) { return _handleCBOREOF(); } } ch = _inputBuffer[_inputPtr++]; type = (ch >> 5) & 0x7; } else { _tagValue = -1; } final int lowBits = ch & 0x1F; switch (type) { case 0: // positive int _numTypesValid = NR_INT; if (lowBits <= 23) { _numberInt = lowBits; } else { switch (lowBits - 24) { case 0: _numberInt = _decode8Bits(); break; case 1: _numberInt = _decode16Bits(); break; case 2: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { int v = _decode32Bits(); if (v >= 0) { _numberInt = v; } else { long l = (long) v; _numberLong = l & 0xFFFFFFFFL; _numTypesValid = NR_LONG; } } break; case 3: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { long l = _decode64Bits(); if (l >= 0L) { _numberLong = l; _numTypesValid = NR_LONG; } else { _numberBigInt = _bigPositive(l); _numTypesValid = NR_BIGINT; } } break; default: _invalidToken(ch); } } return (_currToken = JsonToken.VALUE_NUMBER_INT); case 1: // negative int _numTypesValid = NR_INT; if (lowBits <= 23) { _numberInt = -lowBits - 1; } else { switch (lowBits - 24) { case 0: _numberInt = -_decode8Bits() - 1; break; case 1: _numberInt = -_decode16Bits() - 1; break; case 2: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { int v = _decode32Bits(); if (v < 0) { long unsignedBase = (long) v & 0xFFFFFFFFL; _numberLong = -unsignedBase - 1L; _numTypesValid = NR_LONG; } else { _numberInt = -v - 1; } } break; case 3: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { long l = _decode64Bits(); if (l >= 0L) { _numberLong = -l - 1L; _numTypesValid = NR_LONG; } else { _numberBigInt = _bigNegative(l); _numTypesValid = NR_BIGINT; } } break; default: _invalidToken(ch); } } return (_currToken = JsonToken.VALUE_NUMBER_INT); case 2: // byte[] _typeByte = ch; _tokenIncomplete = true; if (_tagValue >= 0) { return _handleTaggedBinary(_tagValue); } return (_currToken = JsonToken.VALUE_EMBEDDED_OBJECT); case 3: // String _typeByte = ch; _tokenIncomplete = true; return (_currToken = JsonToken.VALUE_STRING); case 4: // Array { int len = _decodeExplicitLength(lowBits); if (_tagValue >= 0) { return _handleTaggedArray(_tagValue, len); } _parsingContext = _parsingContext.createChildArrayContext(len); } return (_currToken = JsonToken.START_ARRAY); case 5: // Object _currToken = JsonToken.START_OBJECT; { int len = _decodeExplicitLength(lowBits); _parsingContext = _parsingContext.createChildObjectContext(len); } return _currToken; case 6: // another tag; not allowed _reportError("Multiple tags not allowed per value (first tag: " + _tagValue + ")"); default: // misc: tokens, floats switch (lowBits) { case 20: return (_currToken = JsonToken.VALUE_FALSE); case 21: return (_currToken = JsonToken.VALUE_TRUE); case 22: return (_currToken = JsonToken.VALUE_NULL); case 23: return (_currToken = _decodeUndefinedValue()); case 25: // 16-bit float... // As per [http://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript] { _numberFloat = (float) _decodeHalfSizeFloat(); _numTypesValid = NR_FLOAT; } return (_currToken = JsonToken.VALUE_NUMBER_FLOAT); case 26: // Float32 { _numberFloat = Float.intBitsToFloat(_decode32Bits()); _numTypesValid = NR_FLOAT; } return (_currToken = JsonToken.VALUE_NUMBER_FLOAT); case 27: // Float64 _numberDouble = Double.longBitsToDouble(_decode64Bits()); _numTypesValid = NR_DOUBLE; return (_currToken = JsonToken.VALUE_NUMBER_FLOAT); case 31: // Break if (_parsingContext.inArray()) { if (!_parsingContext.hasExpectedLength()) { _parsingContext = _parsingContext.getParent(); return (_currToken = JsonToken.END_ARRAY); } } // Object end-marker can't occur here _reportUnexpectedBreak(); } _invalidToken(ch); } return null; } protected String _numberToName(int ch, boolean neg) throws IOException { final int lowBits = ch & 0x1F; int i; if (lowBits <= 23) { i = lowBits; } else { switch (lowBits) { case 24: i = _decode8Bits(); break; case 25: i = _decode16Bits(); break; case 26: i = _decode32Bits(); break; case 27: { long l = _decode64Bits(); if (neg) { l = -l - 1L; } return String.valueOf(l); } default: throw _constructError( "Invalid length indicator for ints (" + lowBits + "), token 0x" + Integer.toHexString(ch)); } } if (neg) { i = -i - 1; } return String.valueOf(i); } protected JsonToken _handleTaggedBinary(int tag) throws IOException { // For now all we should get is BigInteger boolean neg; if (tag == TAG_BIGNUM_POS) { neg = false; } else if (tag == TAG_BIGNUM_NEG) { neg = true; } else { // 12-May-2016, tatu: Since that's all we know, let's otherwise // just return default Binary data marker return (_currToken = JsonToken.VALUE_EMBEDDED_OBJECT); } // First: get the data _finishToken(); BigInteger nr = new BigInteger(_binaryValue); if (neg) { nr = nr.negate(); } _numberBigInt = nr; _numTypesValid = NR_BIGINT; _tagValue = -1; return (_currToken = JsonToken.VALUE_NUMBER_INT); } protected JsonToken _handleTaggedArray(int tag, int len) throws IOException { // For simplicity, let's create matching array context -- in perfect // world that wouldn't be necessarily, but in this one there are // some constraints that make it necessary _parsingContext = _parsingContext.createChildArrayContext(len); // BigDecimal is the only thing we know for sure if (tag != CBORConstants.TAG_DECIMAL_FRACTION) { return (_currToken = JsonToken.START_ARRAY); } _currToken = JsonToken.START_ARRAY; // but has to have length of 2; otherwise we have a problem... if (len != 2) { _reportError("Unexpected array size (" + len + ") for tagged 'bigfloat' value; should have exactly 2 number elements"); } // and then use recursion to get values JsonToken t = nextToken(); // First: exponent, which MUST be a simple integer value if (t != JsonToken.VALUE_NUMBER_INT) { _reportError("Unexpected token (" + t + ") as the first part of 'bigfloat' value: should get VALUE_NUMBER_INT"); } // 27-Nov-2019, tatu: As per [dataformats-binary#139] need to change sign here int exp = -getIntValue(); t = nextToken(); // Should get an integer value; int/long/BigInteger if (t != JsonToken.VALUE_NUMBER_INT) { _reportError("Unexpected token (" + t + ") as the second part of 'bigfloat' value: should get VALUE_NUMBER_INT"); } BigDecimal dec; NumberType numberType = getNumberType(); if (numberType == NumberType.BIG_INTEGER) { dec = new BigDecimal(getBigIntegerValue(), exp); } else { dec = BigDecimal.valueOf(getLongValue(), exp); } t = nextToken(); if (t != JsonToken.END_ARRAY) { _reportError("Unexpected token (" + t + ") after 2 elements of 'bigfloat' value"); } _numberBigDecimal = dec; _numTypesValid = NR_BIGDECIMAL; return (_currToken = JsonToken.VALUE_NUMBER_FLOAT); } // base impl is fine: //public String currentName() throws IOException /** * Method for forcing full read of current token, even if it might otherwise * only be read if data is accessed via {@link #getText} and similar methods. */ @Override public void finishToken() throws IOException { if (_tokenIncomplete) { _finishToken(); } } /* /********************************************************** /* Public API, traversal, optimized: nextFieldName /********************************************************** */ @Override public String nextFieldName() throws IOException { if (_parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { _numTypesValid = NR_UNKNOWN; if (_tokenIncomplete) { _skipIncomplete(); } _tokenInputTotal = _currInputProcessed + _inputPtr; _binaryValue = null; _tagValue = -1; // completed the whole Object? if (!_parsingContext.expectMoreValues()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return null; } // inlined "_decodeFieldName()" if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } final int ch = _inputBuffer[_inputPtr++]; final int type = ((ch >> 5) & 0x7); // offline non-String cases, as they are expected to be rare if (type != CBORConstants.MAJOR_TYPE_TEXT) { if (ch == -1) { // end-of-object, common if (!_parsingContext.hasExpectedLength()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return null; } _reportUnexpectedBreak(); } String name = _decodeNonStringName(ch); _currToken = JsonToken.FIELD_NAME; return name; } final int lenMarker = ch & 0x1F; String name; if (lenMarker <= 23) { if (lenMarker == 0) { name = ""; } else { name = _findDecodedFromSymbols(lenMarker); if (name != null) { _inputPtr += lenMarker; } else { name = _decodeShortName(lenMarker); name = _addDecodedToSymbols(lenMarker, name); } } } else { final int actualLen = _decodeExplicitLength(lenMarker); if (actualLen < 0) { name = _decodeChunkedName(); } else { name = _decodeLongerName(actualLen); } } _parsingContext.setCurrentName(name); _currToken = JsonToken.FIELD_NAME; return name; } // otherwise just fall back to default handling; should occur rarely return (nextToken() == JsonToken.FIELD_NAME) ? currentName() : null; } @Override public boolean nextFieldName(SerializableString str) throws IOException { // Two parsing modes; can only succeed if expecting field name, so handle that first: if (_parsingContext.inObject() && _currToken != JsonToken.FIELD_NAME) { _numTypesValid = NR_UNKNOWN; if (_tokenIncomplete) { _skipIncomplete(); } _tokenInputTotal = _currInputProcessed + _inputPtr; _binaryValue = null; _tagValue = -1; // completed the whole Object? if (!_parsingContext.expectMoreValues()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return false; } byte[] nameBytes = str.asQuotedUTF8(); final int byteLen = nameBytes.length; // fine; require room for up to 2-byte marker, data itself int ptr = _inputPtr; if ((ptr + byteLen + 1) < _inputEnd) { final int ch = _inputBuffer[ptr++]; // only handle usual textual type if (((ch >> 5) & 0x7) == CBORConstants.MAJOR_TYPE_TEXT) { int len = ch & 0x1F; if (len <= 24) { if (len == 23) { len = _inputBuffer[ptr++] & 0xFF; } if (len == byteLen) { for (int i = 0; i < len; ++i) { if (nameBytes[i] != _inputBuffer[ptr + i]) { return str.getValue().equals(nextFieldName()); } } _inputPtr = ptr + byteLen; _parsingContext.setCurrentName(str.getValue()); _currToken = JsonToken.FIELD_NAME; return true; } } } } } // otherwise just fall back to default handling; should occur rarely return str.getValue().equals(nextFieldName()); } @Override public int nextFieldName(FieldNameMatcher matcher) throws IOException { // Two parsing modes; can only succeed if expecting field name, so handle that first: if ((_currToken == JsonToken.FIELD_NAME) || !_parsingContext.inObject()) { nextToken(); return FieldNameMatcher.MATCH_ODD_TOKEN; } if (_tokenIncomplete) { _skipIncomplete(); } _numTypesValid = NR_UNKNOWN; _tokenInputTotal = _currInputProcessed + _inputPtr; _binaryValue = null; _tagValue = -1; // completed the whole Object? if (!_parsingContext.expectMoreValues()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return FieldNameMatcher.MATCH_END_OBJECT; } // inlined "_decodeFieldName()" if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } final int ch = _inputBuffer[_inputPtr++]; final int type = ((ch >> 5) & 0x7); // offline non-String cases, as they are expected to be rare if (type != CBORConstants.MAJOR_TYPE_TEXT) { if (ch == -1) { // end-of-object, common if (!_parsingContext.hasExpectedLength()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return FieldNameMatcher.MATCH_END_OBJECT; } _reportUnexpectedBreak(); } return _nextFieldNameNonText(matcher, ch); } final int lenMarker = ch & 0x1F; // also off-line handling of long(er) names if (lenMarker > 23) { return _nextFieldNameLong(matcher, lenMarker); } if (lenMarker == 0) { return _nextFieldNameEmpty(matcher); } int match = _nextFieldOptimized(matcher, lenMarker); if (match < 0) { // miss... // but if not matched by matcher we got, need to still decode return _nextFieldDecodeAndAdd(matcher, lenMarker); } _inputPtr += lenMarker; final String name = matcher.nameLookup()[match]; _parsingContext.setCurrentName(name); _currToken = JsonToken.FIELD_NAME; return match; } private int _nextFieldDecodeAndAdd(FieldNameMatcher matcher, int len) throws IOException { // 27-Nov-2017, tatu: May already be in main shared symbol table, need to check... String name; final int qlen = (len + 3) >> 2; switch (qlen) { case 1: name = _symbols.findName(_quad1); break; case 2: name = _symbols.findName(_quad1, _quad2); break; case 3: name = _symbols.findName(_quad1, _quad2, _quad3); break; default: name = _symbols.findName(_quadBuffer, qlen); } if (name == null) { name = _decodeShortName(len); name = _addDecodedToSymbols(len, name); } else { _inputPtr += len; } _parsingContext.setCurrentName(name); _currToken = JsonToken.FIELD_NAME; // 07-Feb-2017, tatu: May actually have match in non-quad part (esp. for case-insensitive) return matcher.matchName(name); } private int _nextFieldNameNonText(FieldNameMatcher matcher, int ch) throws IOException { String name = _decodeNonStringName(ch); // NOTE: sets current name too _currToken = JsonToken.FIELD_NAME; /// 15-Nov-2017, tatu: Is this correct? Copied from `nextFieldName()` but... return matcher.matchName(name); } // For presumable rare case of "" private int _nextFieldNameEmpty(FieldNameMatcher matcher) throws IOException { _parsingContext.setCurrentName(""); _currToken = JsonToken.FIELD_NAME; return matcher.matchName(""); } private int _nextFieldNameLong(FieldNameMatcher matcher, int lenMarker) throws IOException { final int actualLen = _decodeExplicitLength(lenMarker); String name; if (actualLen < 0) { name = _decodeChunkedName(); } else { name = _decodeLongerName(actualLen); } _parsingContext.setCurrentName(name); _currToken = JsonToken.FIELD_NAME; return matcher.matchName(name); } private final int _nextFieldOptimized(FieldNameMatcher matcher, final int len) throws IOException { if ((_inputEnd - _inputPtr) < len) { _loadToHaveAtLeast(len); } // First: maybe we already have this name decoded? if (len < 5) { int inPtr = _inputPtr; final byte[] inBuf = _inputBuffer; int q = inBuf[inPtr] & 0xFF; if (len > 1) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 2) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 3) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); } } } _quad1 = q; return matcher.matchByQuad(q); } final byte[] inBuf = _inputBuffer; int inPtr = _inputPtr; // First quadbyte is easy int q1 = (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); if (len < 9) { int q2 = (inBuf[inPtr++] & 0xFF); int left = len - 5; if (left > 0) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 1) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 2) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); } } } _quad1 = q1; _quad2 = q2; return matcher.matchByQuad(q1, q2); } int q2 = (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); if (len < 13) { int q3 = (inBuf[inPtr++] & 0xFF); int left = len - 9; if (left > 0) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 1) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 2) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); } } } _quad1 = q1; _quad2 = q2; _quad3 = q3; return matcher.matchByQuad(q1, q2, q3); } return _nextFieldFromSymbolsLong(matcher, len, q1, q2); } /** * Method for locating names longer than 8 bytes (in UTF-8) */ private final int _nextFieldFromSymbolsLong(FieldNameMatcher matcher, int len, int q1, int q2) throws IOException { // first, need enough buffer to store bytes as ints: { int bufLen = (len + 3) >> 2; if (bufLen > _quadBuffer.length) { _quadBuffer = _growArrayTo(_quadBuffer, bufLen); } } _quadBuffer[0] = q1; _quadBuffer[1] = q2; // then decode, full quads first int offset = 2; int inPtr = _inputPtr + 8; len -= 8; final byte[] inBuf = _inputBuffer; do { int q = (inBuf[inPtr++] & 0xFF); q = (q << 8) | inBuf[inPtr++] & 0xFF; q = (q << 8) | inBuf[inPtr++] & 0xFF; q = (q << 8) | inBuf[inPtr++] & 0xFF; _quadBuffer[offset++] = q; } while ((len -= 4) > 3); // and then leftovers if (len > 0) { int q = inBuf[inPtr] & 0xFF; if (len > 1) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 2) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); } } _quadBuffer[offset++] = q; } return matcher.matchByQuad(_quadBuffer, offset); // return _symbols.findName(_quadBuffer, offset); } /* /********************************************************** /* Public API, traversal, optimized: nextXxxValue /********************************************************** */ @Override public String nextTextValue() throws IOException { _numTypesValid = NR_UNKNOWN; if (_tokenIncomplete) { _skipIncomplete(); } _tokenInputTotal = _currInputProcessed + _inputPtr; _binaryValue = null; _tagValue = -1; if (_parsingContext.inObject()) { if (_currToken != JsonToken.FIELD_NAME) { _tagValue = -1; // completed the whole Object? if (!_parsingContext.expectMoreValues()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_OBJECT; return null; } _currToken = _decodeFieldName(); return null; } } else { if (!_parsingContext.expectMoreValues()) { _tagValue = -1; _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_ARRAY; return null; } } if (_inputPtr >= _inputEnd) { if (!loadMore()) { _handleCBOREOF(); return null; } } int ch = _inputBuffer[_inputPtr++]; int type = (ch >> 5) & 0x7; // One special case: need to consider tag as prefix first: if (type == 6) { _tagValue = Integer.valueOf(_decodeTag(ch & 0x1F)); if (_inputPtr >= _inputEnd) { if (!loadMore()) { _handleCBOREOF(); return null; } } ch = _inputBuffer[_inputPtr++]; type = (ch >> 5) & 0x7; } else { _tagValue = -1; } final int lowBits = ch & 0x1F; switch (type) { case 0: // positive int _numTypesValid = NR_INT; if (lowBits <= 23) { _numberInt = lowBits; } else { switch (lowBits - 24) { case 0: _numberInt = _decode8Bits(); break; case 1: _numberInt = _decode16Bits(); break; case 2: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { int v = _decode32Bits(); if (v < 0) { long l = (long) v; _numberLong = l & 0xFFFFFFFFL; _numTypesValid = NR_LONG; } else { _numberInt = v; } } break; case 3: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { long l = _decode64Bits(); if (l >= 0L) { _numberLong = l; _numTypesValid = NR_LONG; } else { _numberBigInt = _bigPositive(l); _numTypesValid = NR_BIGINT; } } break; default: _invalidToken(ch); } } _currToken = JsonToken.VALUE_NUMBER_INT; return null; case 1: // negative int _numTypesValid = NR_INT; if (lowBits <= 23) { _numberInt = -lowBits - 1; } else { switch (lowBits - 24) { case 0: _numberInt = -_decode8Bits() - 1; break; case 1: _numberInt = -_decode16Bits() - 1; break; case 2: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { int v = _decode32Bits(); if (v < 0) { long unsignedBase = (long) v & 0xFFFFFFFFL; _numberLong = -unsignedBase - 1L; _numTypesValid = NR_LONG; } else { _numberInt = -v - 1; } } break; case 3: // 15-Oct-2016, as per [dataformats-binary#30], we got an edge case here { long l = _decode64Bits(); if (l >= 0L) { _numberLong = l; _numTypesValid = NR_LONG; } else { _numberBigInt = _bigNegative(l); _numTypesValid = NR_BIGINT; } } break; default: _invalidToken(ch); } } _currToken = JsonToken.VALUE_NUMBER_INT; return null; case 2: // byte[] _typeByte = ch; _tokenIncomplete = true; _currToken = JsonToken.VALUE_EMBEDDED_OBJECT; return null; case 3: // String _typeByte = ch; _tokenIncomplete = true; _currToken = JsonToken.VALUE_STRING; return _finishTextToken(ch); case 4: // Array _currToken = JsonToken.START_ARRAY; { int len = _decodeExplicitLength(lowBits); _parsingContext = _parsingContext.createChildArrayContext(len); } return null; case 5: // Object _currToken = JsonToken.START_OBJECT; { int len = _decodeExplicitLength(lowBits); _parsingContext = _parsingContext.createChildObjectContext(len); } return null; case 6: // another tag; not allowed _reportError("Multiple tags not allowed per value (first tag: " + _tagValue + ")"); default: // misc: tokens, floats switch (lowBits) { case 20: _currToken = JsonToken.VALUE_FALSE; return null; case 21: _currToken = JsonToken.VALUE_TRUE; return null; case 22: _currToken = JsonToken.VALUE_NULL; return null; case 23: _currToken = _decodeUndefinedValue(); return null; case 25: // 16-bit float... // As per [http://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript] { _numberFloat = _decodeHalfSizeFloat(); _numTypesValid = NR_FLOAT; } _currToken = JsonToken.VALUE_NUMBER_FLOAT; return null; case 26: // Float32 { _numberFloat = Float.intBitsToFloat(_decode32Bits()); _numTypesValid = NR_FLOAT; } _currToken = JsonToken.VALUE_NUMBER_FLOAT; return null; case 27: // Float64 _numberDouble = Double.longBitsToDouble(_decode64Bits()); _numTypesValid = NR_DOUBLE; _currToken = JsonToken.VALUE_NUMBER_FLOAT; return null; case 31: // Break if (_parsingContext.inArray()) { if (!_parsingContext.hasExpectedLength()) { _parsingContext = _parsingContext.getParent(); _currToken = JsonToken.END_ARRAY; return null; } } // Object end-marker can't occur here _reportUnexpectedBreak(); } _invalidToken(ch); } // otherwise fall back to generic handling: return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null; } @Override public int nextIntValue(int defaultValue) throws IOException { if (nextToken() == JsonToken.VALUE_NUMBER_INT) { return getIntValue(); } return defaultValue; } @Override public long nextLongValue(long defaultValue) throws IOException { if (nextToken() == JsonToken.VALUE_NUMBER_INT) { return getLongValue(); } return defaultValue; } @Override public Boolean nextBooleanValue() throws IOException { JsonToken t = nextToken(); if (t == JsonToken.VALUE_TRUE) { return Boolean.TRUE; } if (t == JsonToken.VALUE_FALSE) { return Boolean.FALSE; } return null; } /* /********************************************************** /* Public API, access to token information, text /********************************************************** */ /** * Method for accessing textual representation of the current event; * if no current event (before first call to {@link #nextToken}, or * after encountering end-of-input), returns null. * Method can be called for any event. */ @Override public String getText() throws IOException { JsonToken t = _currToken; if (_tokenIncomplete) { if (t == JsonToken.VALUE_STRING) { return _finishTextToken(_typeByte); } } if (t == JsonToken.VALUE_STRING) { return _textBuffer.contentsAsString(); } if (t == null) { // null only before/after document return null; } if (t == JsonToken.FIELD_NAME) { return _parsingContext.currentName(); } if (t.isNumeric()) { return getNumberValue().toString(); } return _currToken.asString(); } @Override public char[] getTextCharacters() throws IOException { if (_currToken != null) { // null only before/after document if (_tokenIncomplete) { _finishToken(); } if (_currToken == JsonToken.VALUE_STRING) { return _textBuffer.getTextBuffer(); } if (_currToken == JsonToken.FIELD_NAME) { return _parsingContext.currentName().toCharArray(); } if ((_currToken == JsonToken.VALUE_NUMBER_INT) || (_currToken == JsonToken.VALUE_NUMBER_FLOAT)) { return getNumberValue().toString().toCharArray(); } return _currToken.asCharArray(); } return null; } @Override public int getTextLength() throws IOException { if (_currToken != null) { // null only before/after document if (_tokenIncomplete) { _finishToken(); } if (_currToken == JsonToken.VALUE_STRING) { return _textBuffer.size(); } if (_currToken == JsonToken.FIELD_NAME) { return _parsingContext.currentName().length(); } if ((_currToken == JsonToken.VALUE_NUMBER_INT) || (_currToken == JsonToken.VALUE_NUMBER_FLOAT)) { return getNumberValue().toString().length(); } return _currToken.asCharArray().length; } return 0; } @Override public int getTextOffset() throws IOException { return 0; } @Override public String getValueAsString() throws IOException { // inlined 'getText()' for common case of having String if (_tokenIncomplete) { if (_currToken == JsonToken.VALUE_STRING) { return _finishTextToken(_typeByte); } } if (_currToken == JsonToken.VALUE_STRING) { return _textBuffer.contentsAsString(); } if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) { return null; } return getText(); } @Override public String getValueAsString(String defaultValue) throws IOException { if (_currToken != JsonToken.VALUE_STRING) { if (_currToken == null || _currToken == JsonToken.VALUE_NULL || !_currToken.isScalarValue()) { return defaultValue; } } return getText(); } @Override // since 2.8 public int getText(Writer writer) throws IOException { if (_tokenIncomplete) { _finishToken(); } JsonToken t = _currToken; if (t == JsonToken.VALUE_STRING) { return _textBuffer.contentsToWriter(writer); } if (t == JsonToken.FIELD_NAME) { String n = _parsingContext.currentName(); writer.write(n); return n.length(); } if (t != null) { if (t.isNumeric()) { return _textBuffer.contentsToWriter(writer); } char[] ch = t.asCharArray(); writer.write(ch); return ch.length; } return 0; } /* /********************************************************** /* Public API, access to token information, binary /********************************************************** */ @Override public byte[] getBinaryValue(Base64Variant b64variant) throws IOException { if (_tokenIncomplete) { _finishToken(); } if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT) { // TODO, maybe: support base64 for text? _reportError( "Current token (" + currentToken() + ") not VALUE_EMBEDDED_OBJECT, can not access as binary"); } return _binaryValue; } @Override public Object getEmbeddedObject() throws IOException { if (_tokenIncomplete) { _finishToken(); } if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) { return _binaryValue; } return null; } @Override public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException { if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT) { // Todo, maybe: support base64 for text? _reportError( "Current token (" + currentToken() + ") not VALUE_EMBEDDED_OBJECT, can not access as binary"); } if (!_tokenIncomplete) { // someone already decoded or read if (_binaryValue == null) { // if this method called twice in a row return 0; } final int len = _binaryValue.length; out.write(_binaryValue, 0, len); return len; } _tokenIncomplete = false; int len = _decodeExplicitLength(_typeByte & 0x1F); if (len >= 0) { // non-chunked return _readAndWriteBytes(out, len); } // Chunked... int total = 0; while (true) { len = _decodeChunkLength(CBORConstants.MAJOR_TYPE_BYTES); if (len < 0) { return total; } total += _readAndWriteBytes(out, len); } } private int _readAndWriteBytes(OutputStream out, int total) throws IOException { int left = total; while (left > 0) { int avail = _inputEnd - _inputPtr; if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); avail = _inputEnd - _inputPtr; } int count = Math.min(avail, left); out.write(_inputBuffer, _inputPtr, count); _inputPtr += count; left -= count; } _tokenIncomplete = false; return total; } /* /********************************************************** /* Numeric accessors of public API /********************************************************** */ @Override // since 2.9 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); } if ((_numTypesValid & NR_FLOAT) != 0) { float f = _numberFloat; return Float.isNaN(f) || Float.isInfinite(f); } } return false; } @Override public Number getNumberValue() throws IOException { if (_numTypesValid == NR_UNKNOWN) { _checkNumericValue(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) { return _numberDouble; } if ((_numTypesValid & NR_FLOAT) == 0) { // sanity check _throwInternal(); } return _numberFloat; } @Override public NumberType getNumberType() throws IOException { if (_numTypesValid == NR_UNKNOWN) { _checkNumericValue(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; } if ((_numTypesValid & NR_DOUBLE) != 0) { return NumberType.DOUBLE; } return NumberType.FLOAT; } @Override public int getIntValue() throws IOException { if ((_numTypesValid & NR_INT) == 0) { if (_numTypesValid == NR_UNKNOWN) { // not parsed at all _checkNumericValue(NR_INT); // will also check event type } 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) { _checkNumericValue(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) { _checkNumericValue(NR_BIGINT); } if ((_numTypesValid & NR_BIGINT) == 0) { convertNumberToBigInteger(); } } return _numberBigInt; } @Override public float getFloatValue() throws IOException { if ((_numTypesValid & NR_FLOAT) == 0) { if (_numTypesValid == NR_UNKNOWN) { _checkNumericValue(NR_FLOAT); } if ((_numTypesValid & NR_FLOAT) == 0) { convertNumberToFloat(); } } // 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 _numberFloat; } @Override public double getDoubleValue() throws IOException { if ((_numTypesValid & NR_DOUBLE) == 0) { if (_numTypesValid == NR_UNKNOWN) { _checkNumericValue(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) { _checkNumericValue(NR_BIGDECIMAL); } if ((_numTypesValid & NR_BIGDECIMAL) == 0) { convertNumberToBigDecimal(); } } return _numberBigDecimal; } /* /********************************************************** /* Numeric conversions /********************************************************** */ protected void _checkNumericValue(int expType) throws IOException { // Int or float? if (_currToken == JsonToken.VALUE_NUMBER_INT || _currToken == JsonToken.VALUE_NUMBER_FLOAT) { return; } _reportError("Current token (" + currentToken() + ") not numeric, can not use numeric value accessors"); } 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_FLOAT) != 0) { if (_numberFloat < MIN_INT_D || _numberFloat > MAX_INT_D) { reportOverflowInt(); } _numberInt = (int) _numberFloat; } 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) { if (_numberDouble < MIN_LONG_D || _numberDouble > MAX_LONG_D) { reportOverflowLong(); } _numberLong = (long) _numberDouble; } else if ((_numTypesValid & NR_FLOAT) != 0) { if (_numberFloat < MIN_LONG_D || _numberFloat > MAX_LONG_D) { reportOverflowInt(); } _numberLong = (long) _numberFloat; } 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 if ((_numTypesValid & NR_FLOAT) != 0) { _numberBigInt = BigDecimal.valueOf(_numberFloat).toBigInteger(); } else { _throwInternal(); } _numTypesValid |= NR_BIGINT; } protected void convertNumberToFloat() throws IOException { // 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) { _numberFloat = _numberBigDecimal.floatValue(); } else if ((_numTypesValid & NR_BIGINT) != 0) { _numberFloat = _numberBigInt.floatValue(); } else if ((_numTypesValid & NR_DOUBLE) != 0) { _numberFloat = (float) _numberDouble; } else if ((_numTypesValid & NR_LONG) != 0) { _numberFloat = (float) _numberLong; } else if ((_numTypesValid & NR_INT) != 0) { _numberFloat = (float) _numberInt; } else { _throwInternal(); } _numTypesValid |= NR_FLOAT; } protected void convertNumberToDouble() throws IOException { // 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_FLOAT) != 0) { _numberDouble = (double) _numberFloat; } 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 { // 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 | NR_FLOAT)) != 0) { // Let's 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; } /* /********************************************************** /* Internal methods, secondary parsing /********************************************************** */ /** * Method called to finish parsing of a token so that token contents * are retriable */ protected void _finishToken() throws IOException { _tokenIncomplete = false; int ch = _typeByte; final int type = ((ch >> 5) & 0x7); ch &= 0x1F; // Either String or byte[] if (type != CBORConstants.MAJOR_TYPE_TEXT) { if (type == CBORConstants.MAJOR_TYPE_BYTES) { _binaryValue = _finishBytes(_decodeExplicitLength(ch)); return; } // should never happen so _throwInternal(); } // String value, decode final int len = _decodeExplicitLength(ch); if (len <= 0) { if (len < 0) { _finishChunkedText(); } else { _textBuffer.resetWithEmpty(); } return; } if (len > (_inputEnd - _inputPtr)) { // or if not, could we read? if (len >= _inputBuffer.length) { // If not enough space, need handling similar to chunked _finishLongText(len); return; } _loadToHaveAtLeast(len); } // offline for better optimization _finishShortText(len); } /** * @since 2.6 */ protected String _finishTextToken(int ch) throws IOException { _tokenIncomplete = false; final int type = ((ch >> 5) & 0x7); ch &= 0x1F; // sanity check if (type != CBORConstants.MAJOR_TYPE_TEXT) { // should never happen so _throwInternal(); } // String value, decode final int len = _decodeExplicitLength(ch); if (len <= 0) { if (len == 0) { _textBuffer.resetWithEmpty(); return ""; } _finishChunkedText(); return _textBuffer.contentsAsString(); } if (len > (_inputEnd - _inputPtr)) { // or if not, could we read? if (len >= _inputBuffer.length) { // If not enough space, need handling similar to chunked _finishLongText(len); return _textBuffer.contentsAsString(); } _loadToHaveAtLeast(len); } // offline for better optimization return _finishShortText(len); } private final String _finishShortText(int len) throws IOException { char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); if (outBuf.length < len) { // one minor complication outBuf = _textBuffer.expandCurrentSegment(len); } int outPtr = 0; int inPtr = _inputPtr; _inputPtr += len; final byte[] inputBuf = _inputBuffer; // Let's actually do a tight loop for ASCII first: final int end = inPtr + len; int i; while ((i = inputBuf[inPtr]) >= 0) { outBuf[outPtr++] = (char) i; if (++inPtr == end) { return _textBuffer.setCurrentAndReturn(outPtr); } } final int[] codes = UTF8_UNIT_CODES; do { i = inputBuf[inPtr++] & 0xFF; switch (codes[i]) { case 0: break; case 1: i = ((i & 0x1F) << 6) | (inputBuf[inPtr++] & 0x3F); break; case 2: i = ((i & 0x0F) << 12) | ((inputBuf[inPtr++] & 0x3F) << 6) | (inputBuf[inPtr++] & 0x3F); break; case 3: i = ((i & 0x07) << 18) | ((inputBuf[inPtr++] & 0x3F) << 12) | ((inputBuf[inPtr++] & 0x3F) << 6) | (inputBuf[inPtr++] & 0x3F); // note: this is the codepoint value; need to split, too i -= 0x10000; outBuf[outPtr++] = (char) (0xD800 | (i >> 10)); i = 0xDC00 | (i & 0x3FF); break; default: // invalid _reportError("Invalid byte " + Integer.toHexString(i) + " in Unicode text block"); } outBuf[outPtr++] = (char) i; } while (inPtr < end); return _textBuffer.setCurrentAndReturn(outPtr); } private final void _finishLongText(int len) throws IOException { char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); int outPtr = 0; final int[] codes = UTF8_UNIT_CODES; int outEnd = outBuf.length; while (--len >= 0) { int c = _nextByte() & 0xFF; int code = codes[c]; if (code == 0 && outPtr < outEnd) { outBuf[outPtr++] = (char) c; continue; } if ((len -= code) < 0) { // may need to improve error here but... throw _constructError("Malformed UTF-8 character at end of long (non-chunked) text segment"); } switch (code) { case 0: break; case 1: // 2-byte UTF { int d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = ((c & 0x1F) << 6) | (d & 0x3F); } break; case 2: // 3-byte UTF c = _decodeUTF8_3(c); break; case 3: // 4-byte UTF c = _decodeUTF8_4(c); // Let's add first part right away: outBuf[outPtr++] = (char) (0xD800 | (c >> 10)); if (outPtr >= outBuf.length) { outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; outEnd = outBuf.length; } c = 0xDC00 | (c & 0x3FF); // And let the other char output down below break; default: // Is this good enough error message? _reportInvalidChar(c); } // Need more room? if (outPtr >= outEnd) { outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; outEnd = outBuf.length; } // Ok, let's add char to output: outBuf[outPtr++] = (char) c; } _textBuffer.setCurrentLength(outPtr); } private final void _finishChunkedText() throws IOException { char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); int outPtr = 0; final int[] codes = UTF8_UNIT_CODES; int outEnd = outBuf.length; final byte[] input = _inputBuffer; _chunkEnd = _inputPtr; _chunkLeft = 0; while (true) { // at byte boundary fine to get break marker, hence different: if (_inputPtr >= _chunkEnd) { // end of chunk? get a new one, if there is one; if not, we are done if (_chunkLeft == 0) { int len = _decodeChunkLength(CBORConstants.MAJOR_TYPE_TEXT); if (len < 0) { // fine at this point (but not later) break; } _chunkLeft = len; int end = _inputPtr + len; if (end <= _inputEnd) { // all within buffer _chunkLeft = 0; _chunkEnd = end; } else { // stretches beyond _chunkLeft = (end - _inputEnd); _chunkEnd = _inputEnd; } } // besides of which just need to ensure there's content if (_inputPtr >= _inputEnd) { // end of buffer, but not necessarily chunk loadMoreGuaranteed(); int end = _inputPtr + _chunkLeft; if (end <= _inputEnd) { // all within buffer _chunkLeft = 0; _chunkEnd = end; } else { // stretches beyond _chunkLeft = (end - _inputEnd); _chunkEnd = _inputEnd; } } } int c = input[_inputPtr++] & 0xFF; int code = codes[c]; if (code == 0 && outPtr < outEnd) { outBuf[outPtr++] = (char) c; continue; } switch (code) { case 0: break; case 1: // 2-byte UTF { int d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = ((c & 0x1F) << 6) | (d & 0x3F); } break; case 2: // 3-byte UTF c = _decodeChunkedUTF8_3(c); break; case 3: // 4-byte UTF c = _decodeChunkedUTF8_4(c); // Let's add first part right away: if (outPtr >= outBuf.length) { outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; outEnd = outBuf.length; } outBuf[outPtr++] = (char) (0xD800 | (c >> 10)); c = 0xDC00 | (c & 0x3FF); // And let the other char output down below break; default: // Is this good enough error message? _reportInvalidChar(c); } // Need more room? if (outPtr >= outEnd) { outBuf = _textBuffer.finishCurrentSegment(); outPtr = 0; outEnd = outBuf.length; } // Ok, let's add char to output: outBuf[outPtr++] = (char) c; } _textBuffer.setCurrentLength(outPtr); } private final int _nextByte() throws IOException { int inPtr = _inputPtr; if (inPtr < _inputEnd) { int ch = _inputBuffer[inPtr]; _inputPtr = inPtr + 1; return ch; } loadMoreGuaranteed(); return _inputBuffer[_inputPtr++]; } private final int _nextChunkedByte() throws IOException { int inPtr = _inputPtr; // NOTE: _chunkEnd less than or equal to _inputEnd if (inPtr >= _chunkEnd) { return _nextChunkedByte2(); } int ch = _inputBuffer[inPtr]; _inputPtr = inPtr + 1; return ch; } private final int _nextChunkedByte2() throws IOException { // two possibilities: either end of buffer (in which case, just load more), // or end of chunk if (_inputPtr >= _inputEnd) { // end of buffer, but not necessarily chunk loadMoreGuaranteed(); if (_chunkLeft > 0) { int end = _inputPtr + _chunkLeft; if (end <= _inputEnd) { // all within buffer _chunkLeft = 0; _chunkEnd = end; } else { // stretches beyond _chunkLeft = (end - _inputEnd); _chunkEnd = _inputEnd; } // either way, got it now return _inputBuffer[_inputPtr++]; } } int len = _decodeChunkLength(CBORConstants.MAJOR_TYPE_TEXT); // not actually acceptable if we got a split character if (len < 0) { _reportInvalidEOF(": chunked Text ends with partial UTF-8 character", JsonToken.VALUE_STRING); } int end = _inputPtr + len; if (end <= _inputEnd) { // all within buffer _chunkLeft = 0; _chunkEnd = end; } else { // stretches beyond _chunkLeft = (end - _inputEnd); _chunkEnd = _inputEnd; } // either way, got it now return _inputBuffer[_inputPtr++]; } @SuppressWarnings("resource") protected byte[] _finishBytes(int len) throws IOException { // First, simple: non-chunked if (len >= 0) { if (len == 0) { return NO_BYTES; } byte[] b = new byte[len]; if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int ptr = 0; while (true) { int toAdd = Math.min(len, _inputEnd - _inputPtr); System.arraycopy(_inputBuffer, _inputPtr, b, ptr, toAdd); _inputPtr += toAdd; ptr += toAdd; len -= toAdd; if (len <= 0) { return b; } loadMoreGuaranteed(); } } // or, if not, chunked... ByteArrayBuilder bb = _getByteArrayBuilder(); while (true) { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int ch = _inputBuffer[_inputPtr++] & 0xFF; if (ch == 0xFF) { // end marker break; } // verify that type matches int type = (ch >> 5); if (type != CBORConstants.MAJOR_TYPE_BYTES) { throw _constructError("Mismatched chunk in chunked content: expected " + CBORConstants.MAJOR_TYPE_BYTES + " but encountered " + type); } len = _decodeExplicitLength(ch & 0x1F); if (len < 0) { throw _constructError("Illegal chunked-length indicator within chunked-length value (type " + CBORConstants.MAJOR_TYPE_BYTES + ")"); } while (len > 0) { int avail = _inputEnd - _inputPtr; if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); avail = _inputEnd - _inputPtr; } int count = Math.min(avail, len); bb.write(_inputBuffer, _inputPtr, count); _inputPtr += count; len -= count; } } return bb.toByteArray(); } protected final JsonToken _decodeFieldName() throws IOException { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } final int ch = _inputBuffer[_inputPtr++]; final int type = ((ch >> 5) & 0x7); // Expecting a String, but may need to allow other types too if (type != CBORConstants.MAJOR_TYPE_TEXT) { // the usual case if (ch == -1) { if (!_parsingContext.hasExpectedLength()) { _parsingContext = _parsingContext.getParent(); return JsonToken.END_OBJECT; } _reportUnexpectedBreak(); } // offline non-String cases, as they are expected to be rare _decodeNonStringName(ch); return JsonToken.FIELD_NAME; } final int lenMarker = ch & 0x1F; String name; if (lenMarker <= 23) { if (lenMarker == 0) { name = ""; } else { name = _findDecodedFromSymbols(lenMarker); if (name != null) { _inputPtr += lenMarker; } else { name = _decodeShortName(lenMarker); name = _addDecodedToSymbols(lenMarker, name); } } } else { final int actualLen = _decodeExplicitLength(lenMarker); if (actualLen < 0) { name = _decodeChunkedName(); } else { name = _decodeLongerName(actualLen); } } _parsingContext.setCurrentName(name); return JsonToken.FIELD_NAME; } private final String _decodeShortName(int len) throws IOException { // note: caller ensures we have enough bytes available int outPtr = 0; char[] outBuf = _textBuffer.emptyAndGetCurrentSegment(); if (outBuf.length < len) { // one minor complication outBuf = _textBuffer.expandCurrentSegment(len); } int inPtr = _inputPtr; _inputPtr += len; final int[] codes = UTF8_UNIT_CODES; final byte[] inBuf = _inputBuffer; // First a tight loop for Ascii final int end = inPtr + len; while (true) { int i = inBuf[inPtr] & 0xFF; int code = codes[i]; if (code != 0) { break; } outBuf[outPtr++] = (char) i; if (++inPtr == end) { return _textBuffer.setCurrentAndReturn(outPtr); } } // But in case there's multi-byte char, use a full loop while (inPtr < end) { int i = inBuf[inPtr++] & 0xFF; int code = codes[i]; if (code != 0) { // trickiest one, need surrogate handling switch (code) { case 1: i = ((i & 0x1F) << 6) | (inBuf[inPtr++] & 0x3F); break; case 2: i = ((i & 0x0F) << 12) | ((inBuf[inPtr++] & 0x3F) << 6) | (inBuf[inPtr++] & 0x3F); break; case 3: i = ((i & 0x07) << 18) | ((inBuf[inPtr++] & 0x3F) << 12) | ((inBuf[inPtr++] & 0x3F) << 6) | (inBuf[inPtr++] & 0x3F); // note: this is the codepoint value; need to split, too i -= 0x10000; outBuf[outPtr++] = (char) (0xD800 | (i >> 10)); i = 0xDC00 | (i & 0x3FF); break; default: // invalid _reportError("Invalid byte " + Integer.toHexString(i) + " in Object name"); } } outBuf[outPtr++] = (char) i; } return _textBuffer.setCurrentAndReturn(outPtr); } private final String _decodeLongerName(int len) throws IOException { // Do we have enough buffered content to read? if ((_inputEnd - _inputPtr) < len) { // or if not, could we read? if (len >= _inputBuffer.length) { // If not enough space, need handling similar to chunked _finishLongText(len); return _textBuffer.contentsAsString(); } _loadToHaveAtLeast(len); } String name = _findDecodedFromSymbols(len); if (name != null) { _inputPtr += len; return name; } name = _decodeShortName(len); return _addDecodedToSymbols(len, name); } private final String _decodeChunkedName() throws IOException { _finishChunkedText(); return _textBuffer.contentsAsString(); } /** * Method that handles initial token type recognition for token * that has to be either FIELD_NAME or END_OBJECT. */ protected final String _decodeNonStringName(int ch) throws IOException { final int type = ((ch >> 5) & 0x7); String name; if (type == CBORConstants.MAJOR_TYPE_INT_POS) { name = _numberToName(ch, false); } else if (type == CBORConstants.MAJOR_TYPE_INT_NEG) { name = _numberToName(ch, true); } else if (type == CBORConstants.MAJOR_TYPE_BYTES) { // 08-Sep-2014, tatu: There are codecs (f.ex. Perl module "CBOR::XS") that use Binary data... final int blen = _decodeExplicitLength(ch & 0x1F); byte[] b = _finishBytes(blen); // TODO: Optimize, if this becomes commonly used & bottleneck; we have // more optimized UTF-8 codecs available. name = new String(b, UTF8); } else { if ((ch & 0xFF) == CBORConstants.INT_BREAK) { _reportUnexpectedBreak(); } throw _constructError( "Unsupported major type (" + type + ") for CBOR Objects, not (yet?) supported, only Strings"); } _parsingContext.setCurrentName(name); return name; } private final String _findDecodedFromSymbols(final int len) throws IOException { if ((_inputEnd - _inputPtr) < len) { _loadToHaveAtLeast(len); } // First: maybe we already have this name decoded? if (len < 5) { int inPtr = _inputPtr; final byte[] inBuf = _inputBuffer; int q = inBuf[inPtr] & 0xFF; if (len > 1) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 2) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 3) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); } } } _quad1 = q; return _symbols.findName(q); } final byte[] inBuf = _inputBuffer; int inPtr = _inputPtr; // First quadbyte is easy int q1 = (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF); if (len < 9) { int q2 = (inBuf[inPtr++] & 0xFF); int left = len - 5; if (left > 0) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 1) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 2) { q2 = (q2 << 8) + (inBuf[inPtr++] & 0xFF); } } } _quad1 = q1; _quad2 = q2; return _symbols.findName(q1, q2); } int q2 = (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF); if (len < 13) { int q3 = (inBuf[inPtr++] & 0xFF); int left = len - 9; if (left > 0) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 1) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); if (left > 2) { q3 = (q3 << 8) + (inBuf[inPtr++] & 0xFF); } } } _quad1 = q1; _quad2 = q2; _quad3 = q3; return _symbols.findName(q1, q2, q3); } return _findDecodedLong(len, q1, q2); } /** * Method for locating names longer than 8 bytes (in UTF-8) */ private final String _findDecodedLong(int len, int q1, int q2) throws IOException { // first, need enough buffer to store bytes as ints: { int bufLen = (len + 3) >> 2; if (bufLen > _quadBuffer.length) { _quadBuffer = _growArrayTo(_quadBuffer, bufLen); } } _quadBuffer[0] = q1; _quadBuffer[1] = q2; // then decode, full quads first int offset = 2; int inPtr = _inputPtr + 8; len -= 8; final byte[] inBuf = _inputBuffer; do { int q = (inBuf[inPtr++] & 0xFF); q = (q << 8) | inBuf[inPtr++] & 0xFF; q = (q << 8) | inBuf[inPtr++] & 0xFF; q = (q << 8) | inBuf[inPtr++] & 0xFF; _quadBuffer[offset++] = q; } while ((len -= 4) > 3); // and then leftovers if (len > 0) { int q = inBuf[inPtr] & 0xFF; if (len > 1) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); if (len > 2) { q = (q << 8) + (inBuf[++inPtr] & 0xFF); } } _quadBuffer[offset++] = q; } return _symbols.findName(_quadBuffer, offset); } private final String _addDecodedToSymbols(int len, String name) { if (len < 5) { return _symbols.addName(name, _quad1); } if (len < 9) { return _symbols.addName(name, _quad1, _quad2); } if (len < 13) { return _symbols.addName(name, _quad1, _quad2, _quad3); } int qlen = (len + 3) >> 2; return _symbols.addName(name, _quadBuffer, qlen); } private static int[] _growArrayTo(int[] arr, int minSize) { return Arrays.copyOf(arr, minSize + 4); } /* /********************************************************** /* Internal methods, skipping /********************************************************** */ /** * Method called to skip remainders of an incomplete token, when * contents themselves will not be needed any more. * Only called or byte array and text. */ protected void _skipIncomplete() throws IOException { _tokenIncomplete = false; final int type = ((_typeByte >> 5) & 0x7); // Either String or byte[] if (type != CBORConstants.MAJOR_TYPE_TEXT && type == CBORConstants.MAJOR_TYPE_TEXT) { _throwInternal(); } final int lowBits = _typeByte & 0x1F; if (lowBits <= 23) { if (lowBits > 0) { _skipBytes(lowBits); } return; } switch (lowBits) { case 24: _skipBytes(_decode8Bits()); break; case 25: _skipBytes(_decode16Bits()); break; case 26: _skipBytes(_decode32Bits()); break; case 27: // seriously? _skipBytesL(_decode64Bits()); break; case 31: _skipChunked(type); break; default: _invalidToken(_typeByte); } } protected void _skipChunked(int expectedType) throws IOException { while (true) { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int ch = _inputBuffer[_inputPtr++] & 0xFF; if (ch == 0xFF) { return; } // verify that type matches int type = (ch >> 5); if (type != expectedType) { throw _constructError("Mismatched chunk in chunked content: expected " + expectedType + " but encountered " + type); } final int lowBits = ch & 0x1F; if (lowBits <= 23) { if (lowBits > 0) { _skipBytes(lowBits); } continue; } switch (lowBits) { case 24: _skipBytes(_decode8Bits()); break; case 25: _skipBytes(_decode16Bits()); break; case 26: _skipBytes(_decode32Bits()); break; case 27: // seriously? _skipBytesL(_decode64Bits()); break; case 31: throw _constructError( "Illegal chunked-length indicator within chunked-length value (type " + expectedType + ")"); default: _invalidToken(_typeByte); } } } protected void _skipBytesL(long llen) throws IOException { while (llen > MAX_INT_L) { _skipBytes((int) MAX_INT_L); llen -= MAX_INT_L; } _skipBytes((int) llen); } protected void _skipBytes(int len) throws IOException { while (true) { int toAdd = Math.min(len, _inputEnd - _inputPtr); _inputPtr += toAdd; len -= toAdd; if (len <= 0) { return; } loadMoreGuaranteed(); } } /* /********************************************************** /* Internal methods, length/number decoding /********************************************************** */ private final int _decodeTag(int lowBits) throws IOException { if (lowBits <= 23) { return lowBits; } switch (lowBits - 24) { case 0: return _decode8Bits(); case 1: return _decode16Bits(); case 2: return _decode32Bits(); case 3: // 16-Jan-2014, tatu: Technically legal, but nothing defined, so let's // only allow for cases where encoder is being wasteful... long l = _decode64Bits(); if (l < MIN_INT_L || l > MAX_INT_L) { _reportError("Illegal Tag value: " + l); } return (int) l; } throw _constructError("Invalid low bits for Tag token: 0x" + Integer.toHexString(lowBits)); } /** * Method used to decode explicit length of a variable-length value * (or, for indefinite/chunked, indicate that one is not known). * Note that long (64-bit) length is only allowed if it fits in * 32-bit signed int, for now; expectation being that longer values * are always encoded as chunks. */ private final int _decodeExplicitLength(int lowBits) throws IOException { // common case, indefinite length; relies on marker if (lowBits == 31) { return -1; } if (lowBits <= 23) { return lowBits; } switch (lowBits - 24) { case 0: return _decode8Bits(); case 1: return _decode16Bits(); case 2: return _decode32Bits(); case 3: long l = _decode64Bits(); if (l < 0 || l > MAX_INT_L) { throw _constructError("Illegal length for " + currentToken() + ": " + l); } return (int) l; } throw _constructError("Invalid length for " + currentToken() + ": 0x" + Integer.toHexString(lowBits)); } private int _decodeChunkLength(int expType) throws IOException { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int ch = (int) _inputBuffer[_inputPtr++] & 0xFF; if (ch == CBORConstants.INT_BREAK) { return -1; } int type = (ch >> 5); if (type != expType) { throw _constructError("Mismatched chunk in chunked content: expected " + expType + " but encountered " + type + " (byte 0x" + Integer.toHexString(ch) + ")"); } int len = _decodeExplicitLength(ch & 0x1F); if (len < 0) { throw _constructError( "Illegal chunked-length indicator within chunked-length value (type " + expType + ")"); } return len; } private float _decodeHalfSizeFloat() throws IOException { int i16 = _decode16Bits() & 0xFFFF; boolean neg = (i16 >> 15) != 0; int e = (i16 >> 10) & 0x1F; int f = i16 & 0x03FF; if (e == 0) { float result = (float) (MATH_POW_2_NEG14 * (f / MATH_POW_2_10)); return neg ? -result : result; } if (e == 0x1F) { if (f != 0) return Float.NaN; return neg ? Float.NEGATIVE_INFINITY : Float.POSITIVE_INFINITY; } float result = (float) (Math.pow(2, e - 15) * (1 + f / MATH_POW_2_10)); return neg ? -result : result; } private final int _decode8Bits() throws IOException { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } return _inputBuffer[_inputPtr++] & 0xFF; } private final int _decode16Bits() throws IOException { int ptr = _inputPtr; if ((ptr + 1) >= _inputEnd) { return _slow16(); } final byte[] b = _inputBuffer; int v = ((b[ptr] & 0xFF) << 8) + (b[ptr + 1] & 0xFF); _inputPtr = ptr + 2; return v; } private final int _slow16() throws IOException { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int v = (_inputBuffer[_inputPtr++] & 0xFF); if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } return (v << 8) + (_inputBuffer[_inputPtr++] & 0xFF); } private final int _decode32Bits() throws IOException { int ptr = _inputPtr; if ((ptr + 3) >= _inputEnd) { return _slow32(); } final byte[] b = _inputBuffer; int v = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); _inputPtr = ptr; return v; } private final int _slow32() throws IOException { if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } int v = _inputBuffer[_inputPtr++]; // sign will disappear anyway if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } v = (v << 8) + (_inputBuffer[_inputPtr++] & 0xFF); if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } v = (v << 8) + (_inputBuffer[_inputPtr++] & 0xFF); if (_inputPtr >= _inputEnd) { loadMoreGuaranteed(); } return (v << 8) + (_inputBuffer[_inputPtr++] & 0xFF); } private final long _decode64Bits() throws IOException { int ptr = _inputPtr; if ((ptr + 7) >= _inputEnd) { return _slow64(); } final byte[] b = _inputBuffer; int i1 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); int i2 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16) + ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF); _inputPtr = ptr; return _long(i1, i2); } private final long _slow64() throws IOException { return _long(_decode32Bits(), _decode32Bits()); } private final static long _long(int i1, int i2) { long l1 = i1; long l2 = i2; l2 = (l2 << 32) >>> 32; return (l1 << 32) + l2; } /** * Helper method to encapsulate details of handling of mysterious `undefined` value * that is allowed to be used as something encoder could not handle (as per spec), * whatever the heck that should be. * Current definition for 2.9 is that we will be return {@link JsonToken#VALUE_NULL}, but * for later versions it is likely that we will alternatively allow decoding as * {@link JsonToken#VALUE_EMBEDDED_OBJECT} with "embedded value" of `null`. * * @since 2.9.6 */ protected JsonToken _decodeUndefinedValue() throws IOException { return JsonToken.VALUE_NULL; } /* /********************************************************** /* Internal methods, UTF8 decoding /********************************************************** */ /* private final int X_decodeUTF8_2(int c) throws IOException { int d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } return ((c & 0x1F) << 6) | (d & 0x3F); } */ private final int _decodeUTF8_3(int c1) throws IOException { c1 &= 0x0F; int d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } int c = (c1 << 6) | (d & 0x3F); d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = (c << 6) | (d & 0x3F); return c; } private final int _decodeChunkedUTF8_3(int c1) throws IOException { c1 &= 0x0F; int d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } int c = (c1 << 6) | (d & 0x3F); d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = (c << 6) | (d & 0x3F); return c; } /** * @return Character value <b>minus 0x10000</c>; this so that caller * can readily expand it to actual surrogates */ private final int _decodeUTF8_4(int c) throws IOException { int d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = ((c & 0x07) << 6) | (d & 0x3F); d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = (c << 6) | (d & 0x3F); d = _nextByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } return ((c << 6) | (d & 0x3F)) - 0x10000; } private final int _decodeChunkedUTF8_4(int c) throws IOException { int d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = ((c & 0x07) << 6) | (d & 0x3F); d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } c = (c << 6) | (d & 0x3F); d = _nextChunkedByte(); if ((d & 0xC0) != 0x080) { _reportInvalidOther(d & 0xFF, _inputPtr); } return ((c << 6) | (d & 0x3F)) - 0x10000; } /* /********************************************************** /* Low-level reading, other /********************************************************** */ protected final boolean loadMore() throws IOException { if (_inputStream != null) { _currInputProcessed += _inputEnd; int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length); if (count > 0) { _inputPtr = 0; _inputEnd = count; return true; } // End of input _closeInput(); // Should never return 0, so let's fail if (count == 0) { throw new IOException("InputStream.read() returned 0 characters when trying to read " + _inputBuffer.length + " bytes"); } } return false; } protected final void loadMoreGuaranteed() throws IOException { if (!loadMore()) { _reportInvalidEOF(); } } /** * Helper method that will try to load at least specified number bytes in * input buffer, possible moving existing data around if necessary */ protected final void _loadToHaveAtLeast(int minAvailable) throws IOException { // No input stream, no leading (either we are closed, or have non-stream input source) if (_inputStream == null) { throw _constructError("Needed to read " + minAvailable + " bytes, reached end-of-input"); } // Need to move remaining data in front? int amount = _inputEnd - _inputPtr; if (amount > 0 && _inputPtr > 0) { _currInputProcessed += _inputPtr; //_currInputRowStart -= _inputPtr; System.arraycopy(_inputBuffer, _inputPtr, _inputBuffer, 0, amount); _inputEnd = amount; } else { _inputEnd = 0; } _inputPtr = 0; while (_inputEnd < minAvailable) { int count = _inputStream.read(_inputBuffer, _inputEnd, _inputBuffer.length - _inputEnd); if (count < 1) { // End of input _closeInput(); // Should never return 0, so let's fail if (count == 0) { throw new IOException( "InputStream.read() returned 0 characters when trying to read " + amount + " bytes"); } throw _constructError("Needed to read " + minAvailable + " bytes, missed " + minAvailable + " before end-of-input"); } _inputEnd += count; } } protected ByteArrayBuilder _getByteArrayBuilder() { if (_byteArrayBuilder == null) { _byteArrayBuilder = new ByteArrayBuilder(); } else { _byteArrayBuilder.reset(); } return _byteArrayBuilder; } protected void _closeInput() throws IOException { if (_inputStream != null) { if (_ioContext.isResourceManaged() || isEnabled(StreamReadFeature.AUTO_CLOSE_SOURCE)) { _inputStream.close(); } _inputStream = null; } } @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(_ioContext.getSourceReference())), null); } } /* /********************************************************** /* Internal methods, error handling, reporting /********************************************************** */ protected JsonToken _handleCBOREOF() throws IOException { /* NOTE: here we can and should close input, release buffers, * since this is "hard" EOF, not a boundary imposed by * header token. */ _tagValue = -1; close(); return (_currToken = null); } protected void _invalidToken(int ch) throws JsonParseException { ch &= 0xFF; if (ch == 0xFF) { throw _constructError("Mismatched BREAK byte (0xFF): encountered where value expected"); } throw _constructError("Invalid CBOR value token (first byte): 0x" + Integer.toHexString(ch)); } protected void _reportUnexpectedBreak() throws IOException { if (_parsingContext.inRoot()) { throw _constructError("Unexpected Break (0xFF) token in Root context"); } throw _constructError("Unexpected Break (0xFF) token in definite length (" + _parsingContext.getExpectedLength() + ") " + (_parsingContext.inObject() ? "Object" : "Array")); } protected void _reportInvalidChar(int c) throws JsonParseException { // Either invalid WS or illegal UTF-8 start char if (c < ' ') { _throwInvalidSpace(c); } _reportInvalidInitial(c); } protected void _reportInvalidInitial(int mask) throws JsonParseException { _reportError("Invalid UTF-8 start byte 0x" + Integer.toHexString(mask)); } protected void _reportInvalidOther(int mask) throws JsonParseException { _reportError("Invalid UTF-8 middle byte 0x" + Integer.toHexString(mask)); } protected void _reportInvalidOther(int mask, int ptr) throws JsonParseException { _inputPtr = ptr; _reportInvalidOther(mask); } /* /********************************************************** /* Internal methods, other /********************************************************** */ private final static BigInteger BIT_63 = BigInteger.ONE.shiftLeft(63); private final BigInteger _bigPositive(long l) { BigInteger biggie = BigInteger.valueOf((l << 1) >>> 1); return biggie.or(BIT_63); } private final BigInteger _bigNegative(long l) { // 03-Dec-2017, tatu: [dataformats-binary#149] Careful with overflow BigInteger unsignedBase = _bigPositive(l); return unsignedBase.negate().subtract(BigInteger.ONE); } }