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.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.
/** * 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.
/** * 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); /* /********************************************************** /* Configuration /********************************************************** */
Codec used for data binding when (if) requested.
/** * Codec used for data binding when (if) requested. */
protected ObjectCodec _objectCodec; /* /********************************************************** /* Generic I/O state /********************************************************** */
I/O context for this reader. It handles buffer allocation for the reader.
/** * I/O context for this reader. It handles buffer allocation * for the reader. */
final protected IOContext _ioContext;
Flag that indicates whether parser is closed or not. Gets set when parser is either closed by explicit call (close) or when end-of-input is reached.
/** * Flag that indicates whether parser is closed or not. Gets * set when parser is either closed by explicit call * ({@link #close}) or when end-of-input is reached. */
protected boolean _closed; /* /********************************************************** /* Current input data /********************************************************** */ // Note: type of actual buffer depends on sub-class, can't include
Pointer to next available character in buffer
/** * Pointer to next available character in buffer */
protected int _inputPtr = 0;
Index of character after last available one in the buffer.
/** * 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).
/** * 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.
/** * Current row location of current point in input buffer, starting * from 1, if available. */
protected int _currInputRow = 1;
Current index of the first character of the current row in input buffer. Needed to calculate column position, if necessary; benefit of not having column itself is that this only has to be updated once per line.
/** * Current index of the first character of the current row in input * buffer. Needed to calculate column position, if necessary; benefit * of not having column itself is that this only has to be updated * once per line. */
protected int _currInputRowStart = 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.
/** * 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
/** * Input row on which current token starts, 1-based */
protected int _tokenInputRow = 1;
Column on input row that current token starts; 0-based (although in the end it'll be converted to 1-based)
/** * Column on input row that current token starts; 0-based (although * in the end it'll be converted to 1-based) */
protected int _tokenInputCol = 0; /* /********************************************************** /* Parsing state /********************************************************** */
Information about parser context, context in which the next token is to be parsed (root, array, object).
/** * Information about parser context, context in which * the next token is to be parsed (root, array, object). */
protected 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)
/** * 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 getTextCharacters method (instead of String returning alternatives)
/** * Temporary buffer that is needed if field name is accessed * using {@link #getTextCharacters} method (instead of String * returning alternatives) */
protected char[] _nameCopyBuffer = 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)
/** * 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.
/** * 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 JsonParser.getBinaryValue will not need to decode data more than once.
/** * We will hold on to decoded binary data, for duration of * current event, so that multiple calls to * {@link #getBinaryValue} will not need to decode data more * than once. */
protected byte[] _binaryValue;
We will keep track of tag value for possible future use.
/** * 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.
/** * 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.
/** * 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.

If it is not, it also means that parser can NOT modify underlying buffer.

/** * 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)
/** * 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
/** * Type byte of the current token */
protected int _typeByte;
Helper variables used when dealing with chunked content.
/** * Helper variables used when dealing with chunked content. */
private int _chunkLeft, _chunkEnd; /* /********************************************************** /* Symbol handling, decoding /********************************************************** */
Symbol table that contains field names encountered so far
/** * Symbol table that contains field names encountered so far */
final protected ByteQuadsCanonicalizer _symbols;
Temporary buffer used for name parsing.
/** * Temporary buffer used for name parsing. */
protected int[] _quadBuffer = NO_INTS;
Quads used for hash calculation
/** * Quads used for hash calculation */
protected int _quad1, _quad2, _quad3; /* /********************************************************** /* Constants and fields of former 'JsonNumericParserBase' /********************************************************** */ // 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); // Numeric value holders: multiple fields used for // for efficiency
Bitfield that indicates which numeric representations have been calculated for the current type
/** * Bitfield that indicates which numeric representations * have been calculated for the current type */
protected int _numTypesValid = NR_UNKNOWN; // First primitives protected int _numberInt; protected long _numberLong; protected float _numberFloat; protected double _numberDouble; // And then object types protected BigInteger _numberBigInt; protected BigDecimal _numberBigDecimal; /* /********************************************************** /* Life-cycle /********************************************************** */ public CBORParser(IOContext ctxt, int parserFeatures, int cborFeatures, ObjectCodec codec, ByteQuadsCanonicalizer sym, InputStream in, byte[] inputBuffer, int start, int end, boolean bufferRecyclable) { super(parserFeatures); _ioContext = ctxt; _objectCodec = codec; _symbols = sym; _inputStream = in; _inputBuffer = inputBuffer; _inputPtr = start; _inputEnd = end; _bufferRecyclable = bufferRecyclable; _textBuffer = ctxt.constructTextBuffer(); DupDetector dups = JsonParser.Feature.STRICT_DUPLICATE_DETECTION.enabledIn(parserFeatures) ? DupDetector.rootDetector(this) : null; _parsingContext = CBORReadContext.createRootContext(dups); _tokenInputRow = -1; _tokenInputCol = -1; } @Override public ObjectCodec getCodec() { return _objectCodec; } @Override public void setCodec(ObjectCodec c) { _objectCodec = c; } /* /********************************************************** /* Versioned /********************************************************** */ @Override public Version version() { return PackageVersion.VERSION; } /* /********************************************************** /* Configuration /********************************************************** */ // public JsonParser overrideStdFeatures(int values, int mask) @Override public int getFormatFeatures() { // No parser features, yet return 0; } //public JsonParser overrideFormatFeatures(int values, int mask) { /* /********************************************************** /* 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.
Since:2.5
/** * 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. * * @since 2.5 */
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.
/** * 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.
/** * 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.
/** * Method that can be called to get the name associated with * the current event. */
@Override public String getCurrentName() throws IOException { if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) { CBORReadContext 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: 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 _closeInput (for example, when explicitly closing this reader instance), or separately (if need be).
/** * Method called to release internal buffers owned by the base * reader. This may be called along with {@link #_closeInput} (for * example, when explicitly closing this reader instance), or * separately (if need be). */
protected void _releaseBuffers() throws IOException { 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 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"); } 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 getCurrentName() throws IOException
Method for forcing full read of current token, even if it might otherwise only be read if data is accessed via getText and similar methods.
/** * 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, nextXxxValue/nextFieldName /********************************************************** */ @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 lenMarker = ch & 0x1F; if (lenMarker <= 24) { if (lenMarker == 23) { lenMarker = _inputBuffer[ptr++] & 0xFF; } if (lenMarker == byteLen) { int i = 0; while (true) { if (i == lenMarker) { _inputPtr = ptr+i; _parsingContext.setCurrentName(str.getValue()); _currToken = JsonToken.FIELD_NAME; return true; } if (nameBytes[i] != _inputBuffer[ptr+i]) { break; } ++i; } } } } } } // otherwise just fall back to default handling; should occur rarely return (nextToken() == JsonToken.FIELD_NAME) && str.getValue().equals(getCurrentName()); } @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(); } _decodeNonStringName(ch); _currToken = JsonToken.FIELD_NAME; return getText(); } 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) ? getCurrentName() : null; } @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 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 nextToken, or after encountering end-of-input), returns null. Method can be called for any event.
/** * 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.getCurrentName(); } 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.getCurrentName().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.getCurrentName().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.getCurrentName(); 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 ("+getCurrentToken()+") 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 ("+getCurrentToken()+") 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 ("+getCurrentToken()+") 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
/** * 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
/** * @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.
/** * Method that handles initial token type recognition for token * that has to be either FIELD_NAME or END_OBJECT. */
protected final void _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: As per [Issue#5], 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); } 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)
/** * 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.
/** * 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.
/** * 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 "+getCurrentToken()+": "+l); } return (int) l; } throw _constructError("Invalid length for "+getCurrentToken()+": 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; } /* /********************************************************** /* 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; }
Returns:Character value minus 0x10000; this so that caller can readily expand it to actual surrogates
/** * @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
/** * 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(JsonParser.Feature.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); } }