start adding non-blocking/async json parser (will take a while)
diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java
new file mode 100644
index 0000000..d6d613a
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java
@@ -0,0 +1,472 @@
+package com.fasterxml.jackson.core.json.async;
+
+import java.io.*;
+import java.util.Arrays;
+
+import com.fasterxml.jackson.core.*;
+import com.fasterxml.jackson.core.async.NonBlockingInputFeeder;
+import com.fasterxml.jackson.core.base.ParserBase;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
+
+/**
+ * Intermediate base class for non-blocking JSON parsers.
+ */
+public abstract class NonBlockingParserBase<F extends NonBlockingInputFeeder>
+ extends ParserBase
+{
+ /*
+ /**********************************************************************
+ /* Major state constants
+ /**********************************************************************
+ */
+
+ /**
+ * State right after parser has been constructed, before seeing the first byte
+ * to know if there's header.
+ */
+ protected final static int MAJOR_INITIAL = 0;
+
+ /**
+ * State right after parser a root value has been
+ * finished, but next token has not yet been recognized.
+ */
+ protected final static int MAJOR_ROOT = 1;
+
+ protected final static int MAJOR_OBJECT_FIELD = 2;
+ protected final static int MAJOR_OBJECT_VALUE = 3;
+
+ protected final static int MAJOR_ARRAY_ELEMENT = 4;
+
+ /**
+ * State after non-blocking input source has indicated that no more input
+ * is forthcoming AND we have exhausted all the input
+ */
+ protected final static int MAJOR_CLOSED = 5;
+
+ // // // "Sub-states"
+
+ protected final static int MINOR_FIELD_NAME = 1;
+
+ protected final static int MINOR_VALUE_NUMBER = 6;
+
+ protected final static int MINOR_VALUE_STRING = 15;
+
+ protected final static int MINOR_VALUE_TOKEN_NULL = 15;
+ protected final static int MINOR_VALUE_TOKEN_TRUE = 15;
+ protected final static int MINOR_VALUE_TOKEN_FALSE = 15;
+
+ /*
+ /**********************************************************************
+ /* Helper objects, symbols (field names)
+ /**********************************************************************
+ */
+
+ /**
+ * Symbol table that contains field names encountered so far
+ */
+ final protected ByteQuadsCanonicalizer _symbols;
+
+ /**
+ * Temporary buffer used for name parsing.
+ */
+ protected int[] _quadBuffer = NO_INTS;
+
+ /**
+ * Quads used for hash calculation
+ */
+ protected int _quad1, _quad2;
+
+ /*
+ /**********************************************************************
+ /* Additional parsing state
+ /**********************************************************************
+ */
+
+ /**
+ * Current main decoding state
+ */
+ protected int _majorState;
+
+ /**
+ * Addition indicator within state; contextually relevant for just that state
+ */
+ protected int _minorState;
+
+ /**
+ * Value of {@link #_majorState} after completing a scalar value
+ */
+ protected int _majorStateAfterValue;
+
+ /**
+ * Flag that is sent when calling application indicates that there will
+ * be no more input to parse.
+ */
+ protected boolean _endOfInput = false;
+
+ /*
+ /**********************************************************************
+ /* Other buffering
+ /**********************************************************************
+ */
+
+ /**
+ * Temporary buffer for holding content if input not contiguous (but can
+ * fit in buffer)
+ */
+ protected byte[] _inputCopy;
+
+ /**
+ * Number of bytes buffered in <code>_inputCopy</code>
+ */
+ protected int _inputCopyLen;
+
+ /**
+ * Temporary storage for 32-bit values (int, float), as well as length markers
+ * for length-prefixed values.
+ */
+ protected int _pending32;
+
+ /**
+ * Temporary storage for 64-bit values (long, double), secondary storage
+ * for some other things (scale of BigDecimal values)
+ */
+ protected long _pending64;
+
+ /*
+ /**********************************************************************
+ /* Life-cycle
+ /**********************************************************************
+ */
+
+ public NonBlockingParserBase(IOContext ctxt, int parserFeatures,
+ ByteQuadsCanonicalizer sym)
+ {
+ super(ctxt, parserFeatures);
+ _symbols = sym;
+ // We don't need a lot; for most things maximum known a-priori length below 70 bytes
+ _inputCopy = ctxt.allocReadIOBuffer(500);
+
+ _currToken = null;
+ _majorState = MAJOR_INITIAL;
+ }
+
+ @Override
+ public ObjectCodec getCodec() {
+ return null;
+ }
+
+ @Override
+ public void setCodec(ObjectCodec c) {
+ throw new UnsupportedOperationException("Can not use ObjectMapper with non-blocking parser");
+ }
+
+ /**
+ * @since 2.9
+ */
+ @Override
+ public boolean canParseAsync() { return true; }
+
+ /*
+ /**********************************************************
+ /* Abstract methods from JsonParser
+ /**********************************************************
+ */
+
+ @Override
+ public abstract int releaseBuffered(OutputStream out) throws IOException;
+
+ @Override
+ public Object getInputSource() {
+ // since input is "pushed", to traditional source...
+ return null;
+ }
+
+ @Override
+ protected void _closeInput() throws IOException {
+ // nothing to do here
+ }
+
+ /*
+ /**********************************************************************
+ /* 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;
+ }
+
+ /*
+ /**********************************************************************
+ /* 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) {
+ return _textBuffer.contentsAsString();
+ }
+ JsonToken t = _currToken;
+ if (t == null || _currToken == JsonToken.NOT_AVAILABLE) { // null only before/after document
+ return null;
+ }
+ if (t == JsonToken.FIELD_NAME) {
+ return _parsingContext.getCurrentName();
+ }
+ if (t.isNumeric()) {
+ // TODO: optimize?
+ return getNumberValue().toString();
+ }
+ return _currToken.asString();
+ }
+
+ @Override
+ public char[] getTextCharacters() throws IOException
+ {
+ switch (currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ return _textBuffer.getTextBuffer();
+ case JsonTokenId.ID_FIELD_NAME:
+ if (!_nameCopied) {
+ String name = _parsingContext.getCurrentName();
+ int nameLen = name.length();
+ if (_nameCopyBuffer == null) {
+ _nameCopyBuffer = _ioContext.allocNameCopyBuffer(nameLen);
+ } else if (_nameCopyBuffer.length < nameLen) {
+ _nameCopyBuffer = new char[nameLen];
+ }
+ name.getChars(0, nameLen, _nameCopyBuffer, 0);
+ _nameCopied = true;
+ }
+ return _nameCopyBuffer;
+ case JsonTokenId.ID_NUMBER_INT:
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ return getNumberValue().toString().toCharArray();
+ case JsonTokenId.ID_NO_TOKEN:
+ case JsonTokenId.ID_NOT_AVAILABLE:
+ return null;
+ default:
+ return _currToken.asCharArray();
+ }
+ }
+
+ @Override
+ public int getTextLength() throws IOException
+ {
+ switch (currentTokenId()) {
+ case JsonTokenId.ID_STRING:
+ return _textBuffer.size();
+ case JsonTokenId.ID_FIELD_NAME:
+ return _parsingContext.getCurrentName().length();
+ case JsonTokenId.ID_NUMBER_INT:
+ case JsonTokenId.ID_NUMBER_FLOAT:
+ return getNumberValue().toString().length();
+ case JsonTokenId.ID_NO_TOKEN:
+ case JsonTokenId.ID_NOT_AVAILABLE:
+ return 0; // or throw exception?
+ default:
+ return _currToken.asCharArray().length;
+ }
+ }
+
+ @Override
+ public int getTextOffset() throws IOException
+ {
+ return 0;
+ }
+
+// public abstract int getText(Writer w) throws IOException;
+
+ /*
+ /**********************************************************************
+ /* Public API, access to token information, binary
+ /**********************************************************************
+ */
+
+ @Override
+ public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
+ {
+ if (_currToken != JsonToken.VALUE_EMBEDDED_OBJECT ) {
+ _reportError("Current token (%s) not VALUE_EMBEDDED_OBJECT, can not access as binary", _currToken);
+ }
+ return _binaryValue;
+ }
+
+ @Override
+ public Object getEmbeddedObject() throws IOException
+ {
+ 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 (%s) not VALUE_EMBEDDED_OBJECT, can not access as binary", _currToken);
+ }
+ out.write(_binaryValue);
+ return _binaryValue.length;
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal methods, field name parsing
+ /**********************************************************************
+ */
+
+ // Helper method for trying to find specified encoded UTF-8 byte sequence
+ // from symbol table; if successful avoids actual decoding to String
+ protected final String _findDecodedFromSymbols(byte[] inBuf, int inPtr, int len) throws IOException
+ {
+ // First: maybe we already have this name decoded?
+ if (len < 5) {
+ int q = inBuf[inPtr] & 0xFF;
+ if (--len > 0) {
+ q = (q << 8) + (inBuf[++inPtr] & 0xFF);
+ if (--len > 0) {
+ q = (q << 8) + (inBuf[++inPtr] & 0xFF);
+ if (--len > 0) {
+ q = (q << 8) + (inBuf[++inPtr] & 0xFF);
+ }
+ }
+ }
+ _quad1 = q;
+ return _symbols.findName(q);
+ }
+ if (len < 9) {
+ // First quadbyte is easy
+ int q1 = (inBuf[inPtr] & 0xFF) << 8;
+ q1 += (inBuf[++inPtr] & 0xFF);
+ q1 <<= 8;
+ q1 += (inBuf[++inPtr] & 0xFF);
+ q1 <<= 8;
+ q1 += (inBuf[++inPtr] & 0xFF);
+ int q2 = (inBuf[++inPtr] & 0xFF);
+ len -= 5;
+ if (len > 0) {
+ q2 = (q2 << 8) + (inBuf[++inPtr] & 0xFF);
+ if (--len > 0) {
+ q2 = (q2 << 8) + (inBuf[++inPtr] & 0xFF);
+ if (--len > 0) {
+ q2 = (q2 << 8) + (inBuf[++inPtr] & 0xFF);
+ }
+ }
+ }
+ _quad1 = q1;
+ _quad2 = q2;
+ return _symbols.findName(q1, q2);
+ }
+ return _findDecodedLonger(inBuf, inPtr, len);
+ }
+
+ // Method for locating names longer than 8 bytes (in UTF-8)
+ private final String _findDecodedLonger(byte[] inBuf, int inPtr, int len) throws IOException
+ {
+ // first, need enough buffer to store bytes as ints:
+ {
+ int bufLen = (len + 3) >> 2;
+ if (bufLen > _quadBuffer.length) {
+ _quadBuffer = Arrays.copyOf(_quadBuffer, bufLen+4);
+ }
+ }
+ // then decode, full quads first
+ int offset = 0;
+ do {
+ int q = (inBuf[inPtr++] & 0xFF) << 8;
+ q |= inBuf[inPtr++] & 0xFF;
+ q <<= 8;
+ q |= inBuf[inPtr++] & 0xFF;
+ q <<= 8;
+ q |= inBuf[inPtr++] & 0xFF;
+ _quadBuffer[offset++] = q;
+ } while ((len -= 4) > 3);
+ // and then leftovers
+ if (len > 0) {
+ int q = inBuf[inPtr] & 0xFF;
+ if (--len > 0) {
+ q = (q << 8) + (inBuf[++inPtr] & 0xFF);
+ if (--len > 0) {
+ q = (q << 8) + (inBuf[++inPtr] & 0xFF);
+ }
+ }
+ _quadBuffer[offset++] = q;
+ }
+ return _symbols.findName(_quadBuffer, offset);
+ }
+
+ protected final String _addDecodedToSymbols(int len, String name)
+ {
+ if (len < 5) {
+ return _symbols.addName(name, _quad1, 0);
+ }
+ if (len < 9) {
+ return _symbols.addName(name, _quad1, _quad2);
+ }
+ int qlen = (len + 3) >> 2;
+ return _symbols.addName(name, _quadBuffer, qlen);
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal methods, state changes
+ /**********************************************************************
+ */
+
+ /**
+ * Helper method called at point when all input has been exhausted and
+ * input feeder has indicated no more input will be forthcoming.
+ */
+ protected final JsonToken _eofAsNextToken() throws IOException {
+ _majorState = MAJOR_CLOSED;
+ if (!_parsingContext.inRoot()) {
+ _handleEOF();
+ }
+ close();
+ return (_currToken = null);
+ }
+
+ protected final JsonToken _valueComplete(JsonToken t) throws IOException
+ {
+ _majorState = _majorStateAfterValue;
+ _currToken = t;
+ return t;
+ }
+
+ /*
+ /**********************************************************************
+ /* Internal methods, error reporting
+ /**********************************************************************
+ */
+
+ protected void _reportInvalidInitial(int mask) throws JsonParseException {
+ _reportError("Invalid UTF-8 start byte 0x"+Integer.toHexString(mask));
+ }
+
+ protected void _reportInvalidOther(int mask, int ptr) throws JsonParseException {
+ _inputPtr = ptr;
+ _reportError("Invalid UTF-8 middle byte 0x"+Integer.toHexString(mask));
+ }
+}