ProtobufParser.java
package com.fasterxml.jackson.dataformat.protobuf;
import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
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.util.ByteArrayBuilder;
import com.fasterxml.jackson.core.util.JacksonFeatureSet;
import com.fasterxml.jackson.core.util.TextBuffer;
import com.fasterxml.jackson.core.util.VersionUtil;
import com.fasterxml.jackson.dataformat.protobuf.schema.*;
public class ProtobufParser extends ParserMinimalBase
{
// State constants
// State right after parser created; may start root Object
private final static int STATE_INITIAL = 0;
// State in which we expect another root-object entry key
private final static int STATE_ROOT_KEY = 1;
// State after STATE_ROOT_KEY, when we are about to get a value
// (scalar or structured)
private final static int STATE_ROOT_VALUE = 2;
// Similar to root-key state, but for nested messages
private final static int STATE_NESTED_KEY = 3;
private final static int STATE_NESTED_VALUE = 4;
// State in which an unpacked array is starting
private final static int STATE_ARRAY_START = 5;
private final static int STATE_ARRAY_START_PACKED = 6;
// first array of unpacked array
private final static int STATE_ARRAY_VALUE_FIRST = 7;
// other values of an unpacked array
private final static int STATE_ARRAY_VALUE_OTHER = 8;
private final static int STATE_ARRAY_VALUE_PACKED = 9;
private final static int STATE_ARRAY_END = 10;
// state in which the final END_OBJECT is to be returned
private final static int STATE_MESSAGE_END = 11;
// State after either reaching end-of-input, or getting explicitly closed
private final static int STATE_CLOSED = 12;
private final static int[] UTF8_UNIT_CODES = ProtobufUtil.sUtf8UnitLengths;
// @since 2.14
protected final static JacksonFeatureSet<StreamReadCapability> PROTOBUF_READ_CAPABILITIES
= DEFAULT_READ_CAPABILITIES.with(StreamReadCapability.EXACT_FLOATS);
/*
/**********************************************************
/* Configuration
/**********************************************************
*/
/**
* Codec used for data binding when (if) requested.
*/
protected ObjectCodec _objectCodec;
protected ProtobufSchema _schema;
/*
/**********************************************************
/* Generic I/O state
/**********************************************************
*/
/**
* I/O context for this reader. It handles buffer allocation
* for the reader.
*/
protected final 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;
/*
/**********************************************************
/* 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 ProtobufReadContext _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;
/*
/**********************************************************
/* 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
/**********************************************************
*/
/**
* The innermost Object type ("message" in proto lingo) we are handling.
*/
protected ProtobufMessage _currentMessage;
protected ProtobufField _currentField;
/**
* 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;
/**
* Current state of the parser.
*/
protected int _state = STATE_INITIAL;
protected int _nextTag;
/**
* Length of the value that parser points to, for scalar values that use length
* prefixes (Strings, binary data).
*/
protected int _decodedLength;
protected int _currentEndOffset = Integer.MAX_VALUE;
/*
/**********************************************************
/* Numeric conversions
/**********************************************************
*/
/**
* Bitfield that indicates which numeric representations
* have been calculated for the current type
*/
protected int _numTypesValid = 0;
// First primitives
protected int _numberInt;
protected float _numberFloat;
protected long _numberLong;
protected double _numberDouble;
// And then object types
protected BigInteger _numberBigInt;
protected BigDecimal _numberBigDecimal;
/*
/**********************************************************
/* Life-cycle
/**********************************************************
*/
public ProtobufParser(IOContext ctxt, int parserFeatures,
ObjectCodec codec,
InputStream in, byte[] inputBuffer, int start, int end,
boolean bufferRecyclable)
{
super(parserFeatures, ctxt.streamReadConstraints());
_ioContext = ctxt;
_objectCodec = codec;
_inputStream = in;
_inputBuffer = inputBuffer;
_inputPtr = start;
_inputEnd = end;
_bufferRecyclable = bufferRecyclable;
_textBuffer = ctxt.constructReadConstrainedTextBuffer();
_parsingContext = ProtobufReadContext.createRootContext();
_tokenInputRow = -1;
_tokenInputCol = -1;
}
public void setSchema(ProtobufSchema schema)
{
if (_schema == schema) {
return;
}
if (_state != STATE_INITIAL) {
throw new IllegalStateException("Can not change Schema after parsing has started");
}
_schema = schema;
// start with temporary root...
// _currentContext = _rootContext = ProtobufReadContext.createRootContext(this, schema);
}
@Override
public ObjectCodec getCodec() {
return _objectCodec;
}
@Override
public void setCodec(ObjectCodec c) {
_objectCodec = c;
}
@Override // since 2.12
public JacksonFeatureSet<StreamReadCapability> getReadCapabilities() {
return PROTOBUF_READ_CAPABILITIES;
}
/*
/**********************************************************
/* Versioned
/**********************************************************
*/
@Override
public Version version() {
return PackageVersion.VERSION;
}
/*
/**********************************************************
/* 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 currentLocation()
{
final long offset = _currInputProcessed + _inputPtr;
return new JsonLocation(_ioContext.contentReference(),
offset, // bytes
-1, -1, (int) offset); // 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 currentTokenLocation()
{
// token location is correctly managed...
return new JsonLocation(_ioContext.contentReference(),
_tokenInputTotal, // bytes
-1, -1, (int) _tokenInputTotal); // char offset, line, column
}
@Deprecated // since 2.17
@Override
public JsonLocation getCurrentLocation() { return currentLocation(); }
@Deprecated // since 2.17
@Override
public JsonLocation getTokenLocation() { return currentTokenLocation(); }
/**
* Method that can be called to get the name associated with
* the current event.
*/
@Override // since 2.17
public String currentName() throws IOException
{
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ProtobufReadContext parent = _parsingContext.getParent();
if (parent == null) { // For root level
// jackson-core `ParserBase` just falls through to current but we won't?
return null;
}
return parent.getCurrentName();
}
return _parsingContext.getCurrentName();
}
@Deprecated // since 2.17
@Override
public String getCurrentName() throws IOException { return currentName(); }
@Override
public void overrideCurrentName(String name)
{
// Simple, but need to look for START_OBJECT/ARRAY's "off-by-one" thing:
ProtobufReadContext ctxt = _parsingContext;
if (_currToken == JsonToken.START_OBJECT || _currToken == JsonToken.START_ARRAY) {
ctxt = ctxt.getParent();
if (ctxt == null) { // should we error out or... ?
return;
}
}
ctxt.setCurrentName(name);
}
@Override
public void close() throws IOException
{
_state = STATE_CLOSED;
if (!_closed) {
_closed = true;
try {
_closeInput();
} finally {
// as per [JACKSON-324], do in finally block
// Also, internal buffer(s) can now be released as well
_releaseBuffers();
}
_ioContext.close();
// 17-Jan-2024, tatu: Most code paths won't update context so:
if (!_parsingContext.inRoot()) {
_parsingContext = _parsingContext.getParent();
}
_parsingContext.setCurrentName(null);
}
}
@Override
public boolean isClosed() { return _closed; }
@Override
public ProtobufReadContext getParsingContext() {
return _parsingContext;
}
/*
/**********************************************************
/* Overridden methods
/**********************************************************
*/
@Override
public boolean canUseSchema(FormatSchema schema) {
return (schema instanceof ProtobufSchema);
}
@Override public ProtobufSchema getSchema() {
return _schema;
}
@Override
public void setSchema(FormatSchema schema)
{
if (!(schema instanceof ProtobufSchema)) {
throw new IllegalArgumentException("Can not use FormatSchema of type "
+schema.getClass().getName());
}
setSchema((ProtobufSchema) schema);
}
@Override
public boolean hasTextCharacters()
{
if (_currToken == JsonToken.VALUE_STRING) {
return _textBuffer.hasTextAsCharacters();
}
if (_currToken == JsonToken.FIELD_NAME) {
return _nameCopied;
}
return false;
}
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
{
JsonToken t = nextTokenX();
if (t == JsonToken.FIELD_NAME) {
System.out.print("Field name: "+getCurrentName());
} else if (t == JsonToken.VALUE_NUMBER_INT) {
System.out.print("Int: "+getIntValue());
} else if (t == JsonToken.VALUE_STRING) {
System.out.print("String: '"+getText()+"'");
} else {
System.out.print("Next: "+t);
}
System.out.println(" (state now: "+_state+", ptr "+_inputPtr+")");
return t;
}
public JsonToken nextTokenX() throws IOException {
*/
@Override
public JsonToken nextToken() throws IOException
{
_numTypesValid = NR_UNKNOWN;
// For longer tokens (text, binary), we'll only read when requested
if (_tokenIncomplete) {
_tokenIncomplete = false;
_skipBytes(_decodedLength);
}
_tokenInputTotal = _currInputProcessed + _inputPtr;
// also: clear any data retained so far
_binaryValue = null;
switch (_state) {
case STATE_INITIAL:
if (_schema == null) {
_reportError("No Schema has been assigned: can not decode content");
return null; // never gets here but needed for code analyzers benefit
}
_currentMessage = _schema.getRootType();
_currentField = _currentMessage.firstField();
_state = STATE_ROOT_KEY;
_parsingContext.setMessageType(_currentMessage);
return _updateToken(JsonToken.START_OBJECT);
case STATE_ROOT_KEY:
// end-of-input?
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
close();
return _updateToken(JsonToken.END_OBJECT);
}
}
return _handleRootKey(_decodeVInt());
case STATE_ROOT_VALUE:
{
return _updateToken(_readNextValue(_currentField.type, STATE_ROOT_KEY));
}
case STATE_NESTED_KEY:
if (_checkEnd()) { // will update _parsingContext
return _updateToken(JsonToken.END_OBJECT);
}
return _handleNestedKey(_decodeVInt());
case STATE_ARRAY_START:
_parsingContext = _parsingContext.createChildArrayContext(_currentField);
_streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth());
_state = STATE_ARRAY_VALUE_FIRST;
return _updateToken(JsonToken.START_ARRAY);
case STATE_ARRAY_START_PACKED:
int len = _decodeLength();
int newEnd = _inputPtr + len;
// First: validate that we do not extend past end offset of enclosing message
if (!_parsingContext.inRoot()) {
if (newEnd > _currentEndOffset) {
_reportErrorF("Packed array for field '%s' (of type %s) extends past end of enclosing message: %d > %d (length: %d)",
_currentField.name, _currentMessage.getName(), newEnd, _currentEndOffset, len);
}
}
_currentEndOffset = newEnd;
_parsingContext = _parsingContext.createChildArrayContext(_currentField, newEnd);
_streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth());
_state = STATE_ARRAY_VALUE_PACKED;
return _updateToken(JsonToken.START_ARRAY);
case STATE_ARRAY_VALUE_FIRST: // unpacked
// false -> not root... or should we check?
return _updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_OTHER));
case STATE_ARRAY_VALUE_OTHER: // unpacked
if (_checkEnd()) { // need to check constraints set by surrounding Message (object)
return _updateToken(JsonToken.END_ARRAY);
}
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
ProtobufReadContext parent = _parsingContext.getParent();
// Ok to end if and only if root value
if (!parent.inRoot()) {
_reportInvalidEOF();
}
_parsingContext = parent;
_currentField = parent.getField();
_state = STATE_MESSAGE_END;
return _updateToken(JsonToken.END_ARRAY);
}
}
{
int tag = _decodeVInt();
// expected case: another value in same array
if (_currentField.id == (tag >> 3)) {
return _updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_OTHER));
}
// otherwise, different field, need to end this array
_nextTag = tag;
ProtobufReadContext parent = _parsingContext.getParent();
_parsingContext = parent;
_currentField = parent.getField();
_state = STATE_ARRAY_END;
return _updateToken(JsonToken.END_ARRAY);
}
case STATE_ARRAY_VALUE_PACKED:
if (_checkEnd()) { // need to check constraints of this array itself
return _updateToken(JsonToken.END_ARRAY);
}
return _updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_PACKED));
case STATE_ARRAY_END: // only used with unpacked and with "_nextTag"
// We have returned END_ARRAY; now back to similar to STATE_ROOT_KEY / STATE_NESTED_KEY
// First, similar to STATE_ROOT_KEY:
if (_parsingContext.inRoot()) {
return _handleRootKey(_nextTag);
}
return _handleNestedKey(_nextTag);
case STATE_NESTED_VALUE:
return _updateToken(_readNextValue(_currentField.type, STATE_NESTED_KEY));
case STATE_MESSAGE_END: // occurs if we end with array
close(); // sets state to STATE_CLOSED
return _updateToken(JsonToken.END_OBJECT);
case STATE_CLOSED:
return null;
default:
}
VersionUtil.throwInternal();
return null;
}
private boolean _checkEnd() throws IOException
{
if (_inputPtr < _currentEndOffset) {
return false;
}
if (_inputPtr > _currentEndOffset) {
_reportErrorF("Decoding: current inputPtr (%d) exceeds end offset (%d) (for message of type %s): corrupt content?",
_inputPtr, _currentEndOffset, _currentMessage.getName());
}
ProtobufReadContext parentCtxt = _parsingContext.getParent();
_parsingContext = parentCtxt;
_currentMessage = parentCtxt.getMessageType();
_currentEndOffset = parentCtxt.getEndOffset();
_currentField = parentCtxt.getField();
if (_parsingContext.inRoot()) {
_state = STATE_ROOT_KEY;
} else if (_parsingContext.inArray()) {
_state = _currentField.packed ? STATE_ARRAY_VALUE_PACKED : STATE_ARRAY_VALUE_OTHER;
} else {
_state = STATE_NESTED_KEY;
}
return true;
}
private JsonToken _handleRootKey(int tag) throws IOException
{
int wireType = (tag & 0x7);
final int id = (tag >> 3);
ProtobufField f;
if (_currentField != null) {
if ((f = _currentField.nextOrThisIf(id)) == null) {
if ((f = _currentMessage.field(id)) == null) {
return _skipUnknownField(id, wireType);
}
}
} else {
if ((f = _currentMessage.field(id)) == null) {
return _skipUnknownField(id, wireType);
}
}
_parsingContext.setCurrentName(f.name);
// otherwise quickly validate compatibility
if (!f.isValidFor(wireType)) {
_reportIncompatibleType(f, wireType);
}
// array?
if (f.repeated) {
if (f.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_ROOT_VALUE;
}
_currentField = f;
return _updateToken(JsonToken.FIELD_NAME);
}
private JsonToken _handleNestedKey(int tag) throws IOException
{
int wireType = (tag & 0x7);
int id = (tag >> 3);
ProtobufField f;
if (_currentField != null) {
if ((f = _currentField.nextOrThisIf(id)) == null) {
if ((f = _currentMessage.field(id)) == null) {
return _skipUnknownField(id, wireType);
}
}
} else {
if ((f = _currentMessage.field(id)) == null) {
return _skipUnknownField(id, wireType);
}
}
if ((_currentField == null) || (f = _currentField.nextOrThisIf(id)) == null) {
f = _currentMessage.field(id);
}
// Note: may be null; if so, value needs to be skipped
if (f == null) {
return _skipUnknownField(id, wireType);
}
_parsingContext.setCurrentName(f.name);
if (!f.isValidFor(wireType)) {
_reportIncompatibleType(f, wireType);
}
// array?
if (f.repeated) {
if (f.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_NESTED_VALUE;
}
_currentField = f;
return _updateToken(JsonToken.FIELD_NAME);
}
private JsonToken _readNextValue(FieldType t, int nextState) throws IOException
{
JsonToken type;
switch (_currentField.type) {
case DOUBLE:
_numberDouble = Double.longBitsToDouble(_decode64Bits());
_numTypesValid = NR_DOUBLE;
type = JsonToken.VALUE_NUMBER_FLOAT;
break;
case FLOAT:
_numberFloat = Float.intBitsToFloat(_decode32Bits());
_numTypesValid = NR_FLOAT;
type = JsonToken.VALUE_NUMBER_FLOAT;
break;
case VINT32_Z:
_numberInt = ProtobufUtil.zigzagDecode(_decodeVInt());
_numTypesValid = NR_INT;
type = JsonToken.VALUE_NUMBER_INT;
break;
case VINT64_Z:
_numberLong = ProtobufUtil.zigzagDecode(_decodeVLong());
_numTypesValid = NR_LONG;
type = JsonToken.VALUE_NUMBER_INT;
break;
case VINT32_STD:
_numberInt = _decodeVInt();
_numTypesValid = NR_INT;
type = JsonToken.VALUE_NUMBER_INT;
break;
case VINT64_STD:
_numberLong = _decodeVLong();
_numTypesValid = NR_LONG;
type = JsonToken.VALUE_NUMBER_INT;
break;
case FIXINT32:
_numberInt = _decode32Bits();
_numTypesValid = NR_INT;
type = JsonToken.VALUE_NUMBER_INT;
break;
case FIXINT64:
_numberLong = _decode64Bits();
_numTypesValid = NR_LONG;
type = JsonToken.VALUE_NUMBER_INT;
break;
case BOOLEAN:
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
{
int i = _inputBuffer[_inputPtr++];
// let's be strict here. But keep in mind that it's zigzag encoded so
// we shall value values of '1' and '2'
if (i == 1) {
type = JsonToken.VALUE_TRUE;
} else if (i == 0) {
type = JsonToken.VALUE_FALSE;
} else {
_reportError(String.format("Invalid byte value for bool field %s: 0x%2x; should be either 0x0 or 0x1",
_currentField.name, i));
type = null;
}
}
break;
case STRING:
{
int len = _decodeLength();
_decodedLength = len;
if (len == 0) {
_textBuffer.resetWithEmpty();
} else {
_tokenIncomplete = true;
}
}
type = JsonToken.VALUE_STRING;
break;
case BYTES:
{
int len = _decodeLength();
_decodedLength = len;
if (len == 0) {
_binaryValue = ByteArrayBuilder.NO_BYTES;
} else {
_tokenIncomplete = true;
}
}
type = JsonToken.VALUE_EMBEDDED_OBJECT;
break;
case ENUM:
// 12-Feb-2015, tatu: Can expose as index (int) or name, but internally encoded as VInt.
// So for now, expose as is; may add a feature to choose later on.
// But! May or may not be directly mapped; may need to translate
{
int ix = _decodeLength();
if (_currentField.isStdEnum) {
_numberInt = ix;
_numTypesValid = NR_INT;
type = JsonToken.VALUE_NUMBER_INT;
} else {
// Could translate to better id, but for now let databind
// handle that part
String enumStr = _currentField.findEnumByIndex(ix);
if (enumStr == null) {
_reportErrorF("Unknown id %d (for enum field %s)", ix, _currentField.name);
}
type = JsonToken.VALUE_STRING;
_textBuffer.resetWithString(enumStr);
}
}
break;
case MESSAGE:
{
ProtobufMessage msg = _currentField.getMessageType();
_currentMessage = msg;
int len = _decodeLength();
int newEnd = _inputPtr + len;
// First: validate that we do not extend past end offset of enclosing message
if (newEnd > _currentEndOffset) {
_reportErrorF("Message for field '%s' (of type %s) extends past end of enclosing message: %d > %d (length: %d)",
_currentField.name, msg.getName(), newEnd, _currentEndOffset, len);
}
_currentEndOffset = newEnd;
_state = STATE_NESTED_KEY;
_parsingContext = _parsingContext.createChildObjectContext(msg, _currentField, newEnd);
_streamReadConstraints.validateNestingDepth(_parsingContext.getNestingDepth());
_currentField = msg.firstField();
}
return JsonToken.START_OBJECT;
default:
throw new UnsupportedOperationException("Type "+_currentField.type+" not yet supported");
}
_state = nextState;
return type;
}
private JsonToken _skipUnknownField(int tag, int wireType) throws IOException
{
// First: is this even allowed?
if (!isEnabled(StreamReadFeature.IGNORE_UNDEFINED)) {
_reportErrorF("Undefined property (id %d, wire type %d) for message type %s: not allowed to ignore, as `JsonParser.Feature.IGNORE_UNDEFINED` disabled",
tag, wireType, _currentMessage.getName());
}
while (true) {
_skipUnknownValue(wireType);
// 05-Dec-2017, tatu: as per [#126] seems like we need to check this not just for
// STATE_NESTED_KEY but for arrays too at least?
if (_checkEnd()) { // updates _parsingContext
return _updateToken(JsonToken.END_OBJECT);
}
if (_state == STATE_NESTED_KEY) {
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
} else if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
close();
return _updateToken(JsonToken.END_OBJECT);
}
}
tag = _decodeVInt();
wireType = (tag & 0x7);
// Note: may be null; if so, value needs to be skipped
_currentField = _currentMessage.field(tag >> 3);
if (_currentField == null) {
continue;
}
_parsingContext.setCurrentName(_currentField.name);
_state = STATE_ROOT_VALUE;
// otherwise quickly validate compatibility
if (!_currentField.isValidFor(wireType)) {
_reportIncompatibleType(_currentField, wireType);
}
return _updateToken(JsonToken.FIELD_NAME);
}
}
private void _skipUnknownValue(int wireType) throws IOException
{
switch (wireType) {
case WireType.VINT:
_skipVInt();
break;
case WireType.FIXED_32BIT:
_skipBytes(4);
break;
case WireType.FIXED_64BIT:
_skipBytes(8);
break;
case WireType.LENGTH_PREFIXED:
int len = _decodeLength();
_skipBytes(len);
break;
default:
_reportError(String.format("Unrecognized wire type 0x%x for unknown field within message of type %s)",
wireType, _currentMessage.getName()));
}
}
/*
/**********************************************************
/* Public API, traversal, nextXxxValue/nextFieldName
/**********************************************************
*/
@Override
public boolean nextFieldName(SerializableString sstr) throws IOException
{
if (_state == STATE_ROOT_KEY) {
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
close();
_updateToken(JsonToken.END_OBJECT);
return false;
}
}
final int tag = _decodeVInt();
// inlined _handleRootKey()
final int wireType = (tag & 0x7);
final int id = (tag >> 3);
ProtobufField f = _findField(id);
if (f == null) {
_skipUnknownField(id, wireType);
// may or may not match, but let caller figure it out
return false;
}
String name = _currentField.name;
_parsingContext.setCurrentName(name);
if (!_currentField.isValidFor(wireType)) {
_reportIncompatibleType(_currentField, wireType);
}
// array?
if (_currentField.repeated) {
if (_currentField.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_ROOT_VALUE;
}
_updateToken(JsonToken.FIELD_NAME);
return name.equals(sstr.getValue());
}
if (_state == STATE_NESTED_KEY) {
if (_checkEnd()) { // updates _parsingContext
_updateToken(JsonToken.END_OBJECT);
return false;
}
final int tag = _decodeVInt();
// inlined '_handleNestedKey()'
final int wireType = (tag & 0x7);
final int id = (tag >> 3);
ProtobufField f = _findField(id);
if (f == null) {
_skipUnknownField(id, wireType);
// may or may not match, but let caller figure it out
return false;
}
final String name = _currentField.name;
_parsingContext.setCurrentName(name);
if (!_currentField.isValidFor(wireType)) {
_reportIncompatibleType(_currentField, wireType);
}
// array?
if (_currentField.repeated) {
if (_currentField.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_NESTED_VALUE;
}
_updateToken(JsonToken.FIELD_NAME);
return name.equals(sstr.getValue());
}
return (nextToken() == JsonToken.FIELD_NAME) && sstr.getValue().equals(getCurrentName());
}
@Override
public String nextFieldName() throws IOException
{
if (_state == STATE_ROOT_KEY) {
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
close();
_updateToken(JsonToken.END_OBJECT);
return null;
}
}
final int tag = _decodeVInt();
// inlined _handleRootKey()
int wireType = (tag & 0x7);
final int id = (tag >> 3);
ProtobufField f = _findField(id);
if (f == null) {
if (_skipUnknownField(id, wireType) != JsonToken.FIELD_NAME) {
return null;
}
// sub-optimal as skip method already set it, but:
// [dataformats-binary#202]: need to reset after skipping
wireType = _currentField.wireType;
}
String name = _currentField.name;
_parsingContext.setCurrentName(name);
if (!_currentField.isValidFor(wireType)) {
_reportIncompatibleType(_currentField, wireType);
}
// array?
if (_currentField.repeated) {
if (_currentField.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_ROOT_VALUE;
}
_updateToken(JsonToken.FIELD_NAME);
return name;
}
if (_state == STATE_NESTED_KEY) {
if (_checkEnd()) { // updates _parsingContext
_updateToken(JsonToken.END_OBJECT);
return null;
}
final int tag = _decodeVInt();
// inlined '_handleNestedKey()'
int wireType = (tag & 0x7);
final int id = (tag >> 3);
ProtobufField f = _findField(id);
if (f == null) {
if (_skipUnknownField(id, wireType) != JsonToken.FIELD_NAME) {
return null;
}
// sub-optimal as skip method already set it, but:
// [dataformats-binary#202]: need to reset after skipping
wireType = _currentField.wireType;
}
final String name = _currentField.name;
_parsingContext.setCurrentName(name);
if (!_currentField.isValidFor(wireType)) {
_reportIncompatibleType(_currentField, wireType);
}
// array?
if (_currentField.repeated) {
if (_currentField.packed) {
_state = STATE_ARRAY_START_PACKED;
} else {
_state = STATE_ARRAY_START;
}
} else {
_state = STATE_NESTED_VALUE;
}
_updateToken(JsonToken.FIELD_NAME);
return name;
}
return (nextToken() == JsonToken.FIELD_NAME) ? getCurrentName() : null;
}
@Override
public String nextTextValue() throws IOException
{
// Copied from `nexdtToken()`, as appropriate
_numTypesValid = NR_UNKNOWN;
if (_tokenIncomplete) {
_tokenIncomplete = false;
_skipBytes(_decodedLength);
}
_tokenInputTotal = _currInputProcessed + _inputPtr;
_binaryValue = null;
switch (_state) {
case STATE_ROOT_VALUE:
{
final JsonToken t = _updateToken(_readNextValue(_currentField.type, STATE_ROOT_KEY));
return (t == JsonToken.VALUE_STRING) ? getText() : null;
}
case STATE_NESTED_VALUE:
{
final JsonToken t = _updateToken(_readNextValue(_currentField.type, STATE_NESTED_KEY));
return (t == JsonToken.VALUE_STRING) ? getText() : null;
}
case STATE_ARRAY_VALUE_FIRST: // unpacked
if (_currentField.type == FieldType.STRING) {
_state = STATE_ARRAY_VALUE_OTHER;
break;
}
_updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_OTHER));
return null;
case STATE_ARRAY_VALUE_OTHER: // unpacked
if (_checkEnd()) { // need to check constraints set by surrounding Message (object)
_updateToken(JsonToken.END_ARRAY);
return null;
}
if (_inputPtr >= _inputEnd) {
if (!loadMore()) {
ProtobufReadContext parent = _parsingContext.getParent();
// Ok to end if and only if root value
if (!parent.inRoot()) {
_reportInvalidEOF();
}
_parsingContext = parent;
_currentField = parent.getField();
_state = STATE_MESSAGE_END;
_updateToken(JsonToken.END_ARRAY);
return null;
}
}
{
int tag = _decodeVInt();
// expected case: another value in same array
if (_currentField.id == (tag >> 3)) {
if (_currentField.type == FieldType.STRING) {
break;
}
_updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_OTHER));
return null;
}
// otherwise, different field, need to end this array
_nextTag = tag;
ProtobufReadContext parent = _parsingContext.getParent();
_parsingContext = parent;
_currentField = parent.getField();
}
_state = STATE_ARRAY_END;
_updateToken(JsonToken.END_ARRAY);
return null;
case STATE_ARRAY_VALUE_PACKED:
if (_checkEnd()) { // need to check constraints of this array itself
_updateToken(JsonToken.END_ARRAY);
return null;
}
if (_currentField.type != FieldType.STRING) {
_updateToken(_readNextValue(_currentField.type, STATE_ARRAY_VALUE_PACKED));
return null;
}
break;
default:
return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
}
// At this point we know we have text token so:
final int len = _decodeLength();
_decodedLength = len;
_updateToken(JsonToken.VALUE_STRING);
if (len == 0) {
_textBuffer.resetWithEmpty();
return "";
}
if ((_inputPtr + len) <= _inputEnd) {
return _finishShortText(len);
}
_finishToken();
return _textBuffer.contentsAsString();
}
private final ProtobufField _findField(int id)
{
ProtobufField f;
if ((_currentField == null) || (f = _currentField.nextOrThisIf(id)) == null) {
f = _currentMessage.field(id);
}
_currentField = f;
return f;
}
/*
/**********************************************************
/* 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
{
if (_currToken == JsonToken.VALUE_STRING) {
if (_tokenIncomplete) {
// inlined '_finishToken()`
final int len = _decodedLength;
if ((_inputPtr + len) <= _inputEnd) {
_tokenIncomplete = false;
return _finishShortText(len);
}
_finishToken();
}
return _textBuffer.contentsAsString();
}
// incompleteness ok for binary; won't result in usable text anyway
JsonToken t = _currToken;
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();
}
switch (_currToken) {
case VALUE_STRING:
return _textBuffer.getTextBuffer();
case FIELD_NAME:
return _parsingContext.getCurrentName().toCharArray();
// fall through
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return getNumberValue().toString().toCharArray();
default:
return _currToken.asCharArray();
}
}
return null;
}
@Override
public int getTextLength() throws IOException
{
if (_currToken != null) { // null only before/after document
if (_tokenIncomplete) {
_finishToken();
}
switch (_currToken) {
case VALUE_STRING:
return _textBuffer.size();
case FIELD_NAME:
return _parsingContext.getCurrentName().length();
case VALUE_NUMBER_INT:
case VALUE_NUMBER_FLOAT:
return getNumberValue().toString().length();
default:
// fall through
}
final char[] ch = _currToken.asCharArray();
if (ch != null) {
return ch.length;
}
}
return 0;
}
@Override
public int getTextOffset() throws IOException {
return 0;
}
@Override
public String getValueAsString() throws IOException
{
if (_currToken == JsonToken.VALUE_STRING) {
if (_tokenIncomplete) {
// inlined '_finishToken()`
final int len = _decodedLength;
if ((_inputPtr + len) <= _inputEnd) {
_tokenIncomplete = false;
return _finishShortText(len);
}
_finishToken();
}
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
{
JsonToken t = _currToken;
if (t == JsonToken.VALUE_STRING) {
if (_tokenIncomplete) {
// inlined '_finishToken()`
final int len = _decodedLength;
if ((_inputPtr + len) <= _inputEnd) {
_tokenIncomplete = false;
_finishShortText(len);
} else {
_finishToken();
}
}
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 ("+_currToken+") 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 ) {
_reportError("Current token ("+_currToken+") not VALUE_EMBEDDED_OBJECT, can not access as binary");
}
// !!! TBI
return -1;
}
/*
/**********************************************************
/* Numeric accessors of public API
/**********************************************************
*/
@Override // since 2.9
public boolean isNaN() {
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
if ((_numTypesValid & NR_DOUBLE) != 0) {
return !Double.isFinite(_numberDouble);
}
if ((_numTypesValid & NR_FLOAT) != 0) {
return !Float.isFinite(_numberFloat);
}
}
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 // @since 2.12 -- for (most?) binary formats exactness guaranteed anyway
public final Number getNumberValueExact() throws IOException {
return getNumberValue();
}
@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_LONG) != 0) {
return NumberType.LONG;
}
if ((_numTypesValid & NR_INT) != 0) {
return NumberType.INT;
}
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 // since 2.17
public NumberTypeFP getNumberTypeFP() throws IOException
{
if (_currToken == JsonToken.VALUE_NUMBER_FLOAT) {
if ((_numTypesValid & NR_BIGDECIMAL) != 0) {
return NumberTypeFP.BIG_DECIMAL;
}
if ((_numTypesValid & NR_DOUBLE) != 0) {
return NumberTypeFP.DOUBLE64;
}
if ((_numTypesValid & NR_FLOAT) != 0) {
return NumberTypeFP.FLOAT32;
}
}
return NumberTypeFP.UNKNOWN;
}
@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 ("+_currToken+") 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
_streamReadConstraints.validateBigIntegerScale(_numberBigDecimal.scale());
_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
final String text = getText();
_streamReadConstraints.validateFPLength(text.length());
_numberBigDecimal = NumberInput.parseBigDecimal(
text, isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
} 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;
if (_currToken == JsonToken.VALUE_STRING) {
final int len = _decodedLength;
if (len > (_inputEnd - _inputPtr)) {
// or if not, could we read?
if (len >= _inputBuffer.length) {
// If not enough space, need different handling
_finishLongText(len);
return;
}
_loadToHaveAtLeast(len);
}
// offline for better optimization
_finishShortText(len);
return;
}
if (_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) {
_binaryValue = _finishBytes(_decodedLength);
return;
}
// should never happen but:
_throwInternal();
}
protected byte[] _finishBytes(int len) throws IOException
{
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();
}
}
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?
_reportInvalidInitial(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 _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;
}
/**
* @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 _nextByte() throws IOException {
int inPtr = _inputPtr;
if (inPtr < _inputEnd) {
int ch = _inputBuffer[inPtr];
_inputPtr = inPtr+1;
return ch;
}
loadMoreGuaranteed();
return _inputBuffer[_inputPtr++];
}
/*
/**********************************************************
/* Low-level reading: buffer reload
/**********************************************************
*/
protected final boolean loadMore() throws IOException
{
if (_inputStream != null) {
_currInputProcessed += _inputEnd;
int count = _inputStream.read(_inputBuffer, 0, _inputBuffer.length);
if (count > 0) {
_currentEndOffset = _parsingContext.adjustEnd(_inputEnd);
_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 ptr = _inputPtr;
int amount = _inputEnd - ptr;
if (ptr > 0) {
_currInputProcessed += ptr;
if (amount > 0) {
System.arraycopy(_inputBuffer, ptr, _inputBuffer, 0, amount);
}
_currentEndOffset = _parsingContext.adjustEnd(ptr);
}
_inputPtr = 0;
_inputEnd = amount;
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;
}
}
/*
/**********************************************************
/* Low-level reading: other
/**********************************************************
*/
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.startLocation(
_ioContext.contentReference(), _currInputProcessed)),
null);
}
}
/*
/**********************************************************
/* Helper methods, skipping
/**********************************************************
*/
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();
}
}
protected void _skipVInt() throws IOException
{
int ptr = _inputPtr;
if ((ptr + 10) > _inputEnd) {
_skipVIntSlow();
return;
}
final byte[] buf = _inputBuffer;
// inline checks for first 4 bytes
if ((buf[ptr++] >= 0) || (buf[ptr++] >= 0) || (buf[ptr++] >= 0) || (buf[ptr++] >= 0)) {
_inputPtr = ptr;
return;
}
// but loop beyond
for (int end = ptr+6; ptr < end; ++ptr) {
if (buf[ptr] >= 0) {
_inputPtr = ptr+1;
return;
}
}
_reportTooLongVInt(buf[ptr-1]);
}
protected void _skipVIntSlow() throws IOException
{
for (int i = 0; i < 10; ++i) {
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
int ch = _inputBuffer[_inputPtr++];
if (ch >= 0) {
return;
}
}
_reportTooLongVInt(_inputBuffer[_inputPtr-1]);
}
/*
/**********************************************************
/* Helper methods, decoding
/**********************************************************
*/
private int _decodeVInt() throws IOException
{
int ptr = _inputPtr;
// 5 x 7 = 35 bits -> all we need is 32
if ((ptr + 5) > _inputEnd) {
return _decodeVIntSlow();
}
final byte[] buf = _inputBuffer;
int v = buf[ptr++];
if (v < 0) { // keep going
v &= 0x7F;
// Tag VInts guaranteed to stay in 32 bits, i.e. no more than 5 bytes
int ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 7);
ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 14);
ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 21);
// and now the last byte; at most 4 bits
int last = buf[ptr++] & 0xFF;
if (last > 0x1F) { // should have at most 5 one bits
_inputPtr = ptr;
_reportTooLongVInt(last);
}
v |= (last << 28);
} else {
v |= (ch << 21);
}
} else {
v |= (ch << 14);
}
} else {
v |= (ch << 7);
}
}
_inputPtr = ptr;
return v;
}
// Similar to '_decodeVInt()', but also ensure that no
// negative values allowed
private int _decodeLength() throws IOException
{
int ptr = _inputPtr;
if ((ptr + 5) > _inputEnd) {
int v = _decodeVIntSlow();
if (v < 0) {
_reportInvalidLength(v);
}
return v;
}
final byte[] buf = _inputBuffer;
int v = buf[ptr++];
if (v < 0) { // keep going
v &= 0x7F;
// Tag VInts guaranteed to stay in 32 bits, i.e. no more than 5 bytes
int ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 7);
ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 14);
ch = buf[ptr++];
if (ch < 0) {
v |= ((ch & 0x7F) << 21);
// and now the last byte; at most 4 bits
int last = buf[ptr++] & 0xFF;
if (last > 0x0F) {
_inputPtr = ptr;
_reportTooLongVInt(last);
}
v |= (last << 28);
} else {
v |= (ch << 21);
}
} else {
v |= (ch << 14);
}
} else {
v |= (ch << 7);
}
}
_inputPtr = ptr;
if (v < 0) {
_reportInvalidLength(v);
}
return v;
}
protected int _decodeVIntSlow() throws IOException
{
int v = 0;
int shift = 0;
while (true) {
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
int ch = _inputBuffer[_inputPtr++];
if (shift >= 28) { // must end
ch &= 0xFF;
if (ch > 0x0F) { // should have at most 4 one bits
_reportTooLongVInt(ch);
}
}
if (ch >= 0) {
return v | (ch << shift);
}
v |= ((ch & 0x7f) << shift);
shift += 7;
}
}
private long _decodeVLong() throws IOException
{
// 10 x 7 = 70 bits -> all we need is 64
if ((_inputPtr + 10) > _inputEnd) {
return _decodeVLongSlow();
}
final byte[] buf = _inputBuffer;
// First things first: can start by accumulating as int, first 4 bytes
int v = buf[_inputPtr++];
if (v >= 0) {
return v;
}
v &= 0x7F;
int ch = buf[_inputPtr++];
if (ch >= 0) {
return v | (ch << 7);
}
v |= ((ch & 0x7F) << 7);
ch = buf[_inputPtr++];
if (ch >= 0) {
return v | (ch << 14);
}
v |= ((ch & 0x7F) << 14);
ch = buf[_inputPtr++];
if (ch >= 0) {
return v | (ch << 21);
}
v |= ((ch & 0x7F) << 21);
// 4 bytes gotten. How about 4 more?
long l = (long) v;
v = buf[_inputPtr++];
if (v >= 0) {
return (((long) v) << 28) | l;
}
v &= 0x7F;
ch = buf[_inputPtr++];
if (ch >= 0) {
long l2 = (v | (ch << 7));
return (l2 << 28) | l;
}
v |= ((ch & 0x7F) << 7);
ch = buf[_inputPtr++];
if (ch >= 0) {
long l2 = (v | (ch << 14));
return (l2 << 28) | l;
}
v |= ((ch & 0x7F) << 14);
ch = buf[_inputPtr++];
if (ch >= 0) {
long l2 = (v | (ch << 21));
return (l2 << 28) | l;
}
v |= ((ch & 0x7F) << 21);
// So far so good. Possibly 2 more bytes to get and we are done
l |= (((long) v) << 28);
v = buf[_inputPtr++];
if (v >= 0) {
return (((long) v) << 56) | l;
}
v &= 0x7F;
ch = buf[_inputPtr++] & 0xFF;
if (ch > 0x1) { // error; should have at most 1 bit at the last value
_reportTooLongVInt(ch);
}
v |= ((ch & 0x7F) << 7);
return (((long) v) << 56) | l;
}
protected long _decodeVLongSlow() throws IOException
{
// since only called rarely, no need to optimize int vs long
long v = 0;
int shift = 0;
while (true) {
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
int ch = _inputBuffer[_inputPtr++];
if (shift >= 63) { // must end
ch &= 0xFF;
if (ch > 0x1) { // at most a single bit here
_reportTooLongVLong(ch);
}
}
if (ch >= 0) {
long l = (long) ch;
return v | (l << shift);
}
ch &= 0x7F;
long l = (long) ch;
v |= (l << shift);
shift += 7;
}
}
protected final int _decode32Bits() throws IOException {
int ptr = _inputPtr;
if ((ptr + 3) >= _inputEnd) {
return _slow32();
}
final byte[] b = _inputBuffer;
int v = (b[ptr] & 0xFF) + ((b[ptr+1] & 0xFF) << 8)
+ ((b[ptr+2] & 0xFF) << 16) + ((b[ptr+3] & 0xFF) << 24);
_inputPtr = ptr+4;
return v;
}
protected final int _slow32() throws IOException {
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
int v = _inputBuffer[_inputPtr++] & 0xFF;
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
v |= ((_inputBuffer[_inputPtr++] & 0xFF) << 8);
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
v |= ((_inputBuffer[_inputPtr++] & 0xFF) << 16);
if (_inputPtr >= _inputEnd) {
loadMoreGuaranteed();
}
return v | (_inputBuffer[_inputPtr++] << 24); // sign will shift away
}
protected final long _decode64Bits() throws IOException {
int ptr = _inputPtr;
if ((ptr + 7) >= _inputEnd) {
return _slow64();
}
final byte[] b = _inputBuffer;
int i1 = (b[ptr++] & 0xFF) | ((b[ptr++] & 0xFF) << 8)
| ((b[ptr++] & 0xFF) << 16) | (b[ptr++] << 24);
int i2 = (b[ptr++] & 0xFF) | ((b[ptr++] & 0xFF) << 8)
| ((b[ptr++] & 0xFF) << 16) | (b[ptr++] << 24);
_inputPtr = ptr;
return _long(i1, i2);
}
protected final long _slow64() throws IOException {
return _long(_decode32Bits(), _decode32Bits());
}
protected final static long _long(int i1, int i2)
{
// important: LSB all the way, hence:
long high = i2;
high <<= 32;
long low = i1;
low = (low << 32) >>> 32;
return high | low;
}
/*
/**********************************************************
/* Helper methods, error reporting
/**********************************************************
*/
private void _reportErrorF(String format, Object... args) throws JsonParseException {
_reportError(String.format(format, args));
}
private void _reportIncompatibleType(ProtobufField field, int wireType) throws JsonParseException
{
_reportError(String.format
("Incompatible wire type (0x%x) for field '%s': not valid for field of type %s (expected 0x%x)",
wireType, field.name, field.type, field.type.getWireType()));
}
private void _reportInvalidLength(int len) throws JsonParseException {
_reportError("Invalid length (%d): must be positive number", len);
}
private void _reportTooLongVInt(int fifth) throws JsonParseException {
_reportError("Too long tag VInt: fifth byte 0x%x", fifth);
}
private void _reportTooLongVLong(int fifth) throws JsonParseException {
_reportError("Too long tag VLong: tenth byte 0x%x", fifth);
}
private void _reportInvalidInitial(int mask) throws JsonParseException {
_reportError("Invalid UTF-8 start byte 0x%x", mask);
}
private void _reportInvalidOther(int mask) throws JsonParseException {
_reportError("Invalid UTF-8 middle byte 0x%x", mask);
}
private void _reportInvalidOther(int mask, int ptr) throws JsonParseException {
_inputPtr = ptr;
_reportInvalidOther(mask);
}
}