Add one more puzzle-piece for async parsing api
diff --git a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
index 97cd004..eb93746 100644
--- a/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
+++ b/src/main/java/com/fasterxml/jackson/core/JsonFactory.java
@@ -12,6 +12,7 @@
import com.fasterxml.jackson.core.format.MatchStrength;
import com.fasterxml.jackson.core.io.*;
import com.fasterxml.jackson.core.json.*;
+import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser;
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
import com.fasterxml.jackson.core.util.BufferRecycler;
@@ -738,7 +739,7 @@
/*
/**********************************************************
- /* Parser factories (new ones, as per [Issue-25])
+ /* Parser factories, traditional (blocking) I/O sources
/**********************************************************
*/
@@ -926,6 +927,12 @@
}
/**
+ * Optional method for constructing parser for reading contents from specified {@link DataInput}
+ * instance.
+ *<p>
+ * If this factory does not support {@link DataInput} as source,
+ * will throw {@link UnsupportedOperationException}
+ *
* @since 2.8
*/
public JsonParser createParser(DataInput in) throws IOException {
@@ -935,6 +942,34 @@
/*
/**********************************************************
+ /* Parser factories, non-blocking (async) sources
+ /**********************************************************
+ */
+
+ /**
+ * Optional method for constructing parser for non-blocking parsing
+ * via {@link com.fasterxml.jackson.core.async.ByteArrayFeeder}
+ * interface (accessed using {@link JsonParser#getNonBlockingInputFeeder()}
+ * from constructed instance).
+ *<p>
+ * If this factory does not support non-blocking parsing (either at all,
+ * or from byte array),
+ * will throw {@link UnsupportedOperationException}
+ *
+ * @since 2.9
+ */
+ public JsonParser createNonBlockingByteArrayParser() throws IOException
+ {
+ // 17-May-2017, tatu: Need to take care not to accidentally create JSON parser
+ // for non-JSON input:
+ _requireJSONFactory("Non-blocking source not (yet?) support for this format (%s)");
+ IOContext ctxt = _createContext(null, false);
+ ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChild(_factoryFeatures);
+ return new NonBlockingJsonParser(ctxt, _parserFeatures, can);
+ }
+
+ /*
+ /**********************************************************
/* Parser factories (old ones, pre-2.2)
/**********************************************************
*/
@@ -1332,17 +1367,15 @@
}
/**
+ * Optional factory method, expected to be overridden
+ *
* @since 2.8
*/
protected JsonParser _createParser(DataInput input, IOContext ctxt) throws IOException
{
// 13-May-2016, tatu: Need to take care not to accidentally create JSON parser for
- // non-JSON input. So, bit unclean but...
- String format = getFormatName();
- if (format != FORMAT_NAME_JSON) { // NOTE: only ensure override; full equality NOT needed
- throw new UnsupportedOperationException(String.format(
- "InputData source not (yet?) support for this format (%s)", format));
- }
+ // non-JSON input.
+ _requireJSONFactory("InputData source not (yet?) support for this format (%s)");
// Also: while we can't do full bootstrapping (due to read-ahead limitations), should
// at least handle possible UTF-8 BOM
int firstByte = ByteSourceJsonBootstrapper.skipUTF8BOM(input);
@@ -1558,4 +1591,31 @@
}
return url.openStream();
}
+
+ /*
+ /**********************************************************
+ /* Internal helper methods
+ /**********************************************************
+ */
+
+ /**
+ * Helper method called to work around the problem of this class both defining
+ * general API for constructing parsers+generators AND implementing the API
+ * for JSON handling. Problem here is that when adding new functionality
+ * via factory methods, it is not possible to leave these methods abstract
+ * (because we are implementing them for JSON); but there is risk that
+ * sub-classes do not override them all (plus older version can not implement).
+ * So a work-around is to add a check to ensure that factory is still one
+ * used for JSON; and if not, make base implementation of a factory method fail.
+ *
+ * @since 2.9
+ */
+ private final void _requireJSONFactory(String msg) {
+ // NOTE: since we only really care about whether this is standard JSON-backed factory,
+ // or its sub-class / delegated to one, no need to check for equality, identity is enough
+ String format = getFormatName();
+ if (format != FORMAT_NAME_JSON) {
+ throw new UnsupportedOperationException(String.format(msg, format));
+ }
+ }
}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java
new file mode 100644
index 0000000..6c67e26
--- /dev/null
+++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParser.java
@@ -0,0 +1,220 @@
+package com.fasterxml.jackson.core.json.async;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.async.ByteArrayFeeder;
+import com.fasterxml.jackson.core.async.NonBlockingInputFeeder;
+import com.fasterxml.jackson.core.io.IOContext;
+import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
+import com.fasterxml.jackson.core.util.VersionUtil;
+
+public class NonBlockingJsonParser
+ extends NonBlockingJsonParserBase
+ implements ByteArrayFeeder
+{
+ /*
+ /**********************************************************************
+ /* Input source config
+ /**********************************************************************
+ */
+
+ /**
+ * This buffer is actually provided via {@link NonBlockingInputFeeder}
+ */
+ protected byte[] _inputBuffer = NO_BYTES;
+
+ /**
+ * In addition to current buffer pointer, and end pointer,
+ * we will also need to know number of bytes originally
+ * contained. This is needed to correctly update location
+ * information when the block has been completed.
+ */
+ protected int _origBufferLen;
+
+ // And from ParserBase:
+// protected int _inputPtr;
+// protected int _inputEnd;
+
+ /*
+ /**********************************************************************
+ /* Life-cycle
+ /**********************************************************************
+ */
+
+ public NonBlockingJsonParser(IOContext ctxt, int parserFeatures,
+ ByteQuadsCanonicalizer sym)
+ {
+ super(ctxt, parserFeatures, sym);
+ }
+
+ /*
+ /**********************************************************************
+ /* AsyncInputFeeder impl
+ /**********************************************************************
+ */
+
+ @Override
+ public ByteArrayFeeder getNonBlockingInputFeeder() {
+ return this;
+ }
+
+ @Override
+ public final boolean needMoreInput() {
+ return (_inputPtr >=_inputEnd) && !_endOfInput;
+ }
+
+ @Override
+ public void feedInput(byte[] buf, int start, int end) throws IOException
+ {
+ // Must not have remaining input
+ if (_inputPtr < _inputEnd) {
+ _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr);
+ }
+ if (end < start) {
+ _reportError("Input end (%d) may not be before start (%d)", end, start);
+ }
+ // and shouldn't have been marked as end-of-input
+ if (_endOfInput) {
+ _reportError("Already closed, can not feed more input");
+ }
+ // Time to update pointers first
+ _currInputProcessed += _origBufferLen;
+
+ // And then update buffer settings
+ _inputBuffer = buf;
+ _inputPtr = start;
+ _inputEnd = end;
+ _origBufferLen = end - start;
+ }
+
+ @Override
+ public void endOfInput() {
+ _endOfInput = true;
+ }
+
+ /*
+ /**********************************************************************
+ /* Abstract methods/overrides from JsonParser
+ /**********************************************************************
+ */
+
+ /* Implementing these methods efficiently for non-blocking cases would
+ * be complicated; so for now let's just use the default non-optimized
+ * implementation
+ */
+
+// public boolean nextFieldName(SerializableString str) throws IOException
+// public String nextTextValue() throws IOException
+// public int nextIntValue(int defaultValue) throws IOException
+// public long nextLongValue(long defaultValue) throws IOException
+// public Boolean nextBooleanValue() throws IOException
+
+ @Override
+ public int releaseBuffered(OutputStream out) throws IOException {
+ int avail = _inputEnd - _inputPtr;
+ if (avail > 0) {
+ out.write(_inputBuffer, _inputPtr, avail);
+ }
+ return avail;
+ }
+
+ /*
+ /**********************************************************************
+ /* Main-level decoding
+ /**********************************************************************
+ */
+
+ @Override
+ public JsonToken nextToken() throws IOException
+ {
+ // First: regardless of where we really are, need at least one more byte;
+ // can simplify some of the checks by short-circuiting right away
+ if (_inputPtr >= _inputEnd) {
+ if (_closed) {
+ return null;
+ }
+ // note: if so, do not even bother changing state
+ if (_endOfInput) { // except for this special case
+ return _eofAsNextToken();
+ }
+ return JsonToken.NOT_AVAILABLE;
+ }
+ // in the middle of tokenization?
+ if (_currToken == JsonToken.NOT_AVAILABLE) {
+ return _finishToken();
+ }
+
+ // No: fresh new token; may or may not have existing one
+ _numTypesValid = NR_UNKNOWN;
+// _tokenInputTotal = _currInputProcessed + _inputPtr;
+ // also: clear any data retained so far
+ _binaryValue = null;
+ int ch = _inputBuffer[_inputPtr++];
+
+ switch (_majorState) {
+ case MAJOR_INITIAL:
+ // TODO: Bootstrapping? BOM?
+
+ case MAJOR_ROOT:
+ return _startValue(ch);
+
+ case MAJOR_OBJECT_FIELD: // field or end-object
+ // expect name
+ return _startFieldName(ch);
+
+ case MAJOR_OBJECT_VALUE:
+ case MAJOR_ARRAY_ELEMENT: // element or end-array
+ return _startValue(ch);
+
+ default:
+ }
+ VersionUtil.throwInternal();
+ return null;
+ }
+
+ /**
+ * Method called when a (scalar) value type has been detected, but not all of
+ * contents have been decoded due to incomplete input available.
+ */
+ protected final JsonToken _finishToken() throws IOException
+ {
+ // NOTE: caller ensures availability of at least one byte
+
+ switch (_minorState) {
+ }
+ return null;
+ }
+
+ /*
+ /**********************************************************************
+ /* Second-level decoding, root level
+ /**********************************************************************
+ */
+
+ /**
+ * Helper method called to detect type of a value token (at any level), and possibly
+ * decode it if contained in input buffer.
+ * Note that possible header has been ruled out by caller and is not checked here.
+ */
+ private final JsonToken _startValue(int ch) throws IOException
+ {
+ return null;
+ }
+
+ /*
+ /**********************************************************************
+ /* Second-level decoding, Name decoding
+ /**********************************************************************
+ */
+
+ /**
+ * Method that handles initial token type recognition for token
+ * that has to be either FIELD_NAME or END_OBJECT.
+ */
+ protected final JsonToken _startFieldName(int ch) throws IOException
+ {
+ return null;
+ }
+}
diff --git a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java
similarity index 93%
rename from src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java
rename to src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java
index d6d613a..4955417 100644
--- a/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingParserBase.java
+++ b/src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java
@@ -4,7 +4,6 @@
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;
@@ -12,7 +11,7 @@
/**
* Intermediate base class for non-blocking JSON parsers.
*/
-public abstract class NonBlockingParserBase<F extends NonBlockingInputFeeder>
+public abstract class NonBlockingJsonParserBase
extends ParserBase
{
/*
@@ -139,7 +138,7 @@
/**********************************************************************
*/
- public NonBlockingParserBase(IOContext ctxt, int parserFeatures,
+ public NonBlockingJsonParserBase(IOContext ctxt, int parserFeatures,
ByteQuadsCanonicalizer sym)
{
super(ctxt, parserFeatures);
@@ -169,6 +168,16 @@
/*
/**********************************************************
+ /* Test support
+ /**********************************************************
+ */
+
+ protected ByteQuadsCanonicalizer symbolTableForTests() {
+ return _symbols;
+ }
+
+ /*
+ /**********************************************************
/* Abstract methods from JsonParser
/**********************************************************
*/
@@ -290,13 +299,23 @@
}
@Override
- public int getTextOffset() throws IOException
- {
+ public int getTextOffset() throws IOException {
return 0;
}
-// public abstract int getText(Writer w) throws IOException;
-
+ @Override
+ public int getText(Writer w) throws IOException
+ {
+ if (_currToken == JsonToken.VALUE_STRING) {
+ return _textBuffer.contentsToWriter(w);
+ }
+ if (_currToken == JsonToken.NOT_AVAILABLE) {
+ _reportError("Current token not available: can not call this method");
+ }
+ // otherwise default handling works fine
+ return super.getText(w);
+ }
+
/*
/**********************************************************************
/* Public API, access to token information, binary
@@ -420,7 +439,7 @@
protected final String _addDecodedToSymbols(int len, String name)
{
if (len < 5) {
- return _symbols.addName(name, _quad1, 0);
+ return _symbols.addName(name, _quad1);
}
if (len < 9) {
return _symbols.addName(name, _quad1, _quad2);