Skip to content

Commit 81fb43c

Browse files
committed
Add one more puzzle-piece for async parsing api
1 parent 03ed210 commit 81fb43c

File tree

3 files changed

+314
-15
lines changed

3 files changed

+314
-15
lines changed

src/main/java/com/fasterxml/jackson/core/JsonFactory.java

+67-7
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.core.format.MatchStrength;
1313
import com.fasterxml.jackson.core.io.*;
1414
import com.fasterxml.jackson.core.json.*;
15+
import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser;
1516
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
1617
import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
1718
import com.fasterxml.jackson.core.util.BufferRecycler;
@@ -738,7 +739,7 @@ public JsonFactory setCodec(ObjectCodec oc) {
738739

739740
/*
740741
/**********************************************************
741-
/* Parser factories (new ones, as per [Issue-25])
742+
/* Parser factories, traditional (blocking) I/O sources
742743
/**********************************************************
743744
*/
744745

@@ -926,13 +927,47 @@ public JsonParser createParser(char[] content, int offset, int len) throws IOExc
926927
}
927928

928929
/**
930+
* Optional method for constructing parser for reading contents from specified {@link DataInput}
931+
* instance.
932+
*<p>
933+
* If this factory does not support {@link DataInput} as source,
934+
* will throw {@link UnsupportedOperationException}
935+
*
929936
* @since 2.8
930937
*/
931938
public JsonParser createParser(DataInput in) throws IOException {
932939
IOContext ctxt = _createContext(in, false);
933940
return _createParser(_decorate(in, ctxt), ctxt);
934941
}
935942

943+
/*
944+
/**********************************************************
945+
/* Parser factories, non-blocking (async) sources
946+
/**********************************************************
947+
*/
948+
949+
/**
950+
* Optional method for constructing parser for non-blocking parsing
951+
* via {@link com.fasterxml.jackson.core.async.ByteArrayFeeder}
952+
* interface (accessed using {@link JsonParser#getNonBlockingInputFeeder()}
953+
* from constructed instance).
954+
*<p>
955+
* If this factory does not support non-blocking parsing (either at all,
956+
* or from byte array),
957+
* will throw {@link UnsupportedOperationException}
958+
*
959+
* @since 2.9
960+
*/
961+
public JsonParser createNonBlockingByteArrayParser() throws IOException
962+
{
963+
// 17-May-2017, tatu: Need to take care not to accidentally create JSON parser
964+
// for non-JSON input:
965+
_requireJSONFactory("Non-blocking source not (yet?) support for this format (%s)");
966+
IOContext ctxt = _createContext(null, false);
967+
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChild(_factoryFeatures);
968+
return new NonBlockingJsonParser(ctxt, _parserFeatures, can);
969+
}
970+
936971
/*
937972
/**********************************************************
938973
/* Parser factories (old ones, pre-2.2)
@@ -1332,17 +1367,15 @@ protected JsonParser _createParser(byte[] data, int offset, int len, IOContext c
13321367
}
13331368

13341369
/**
1370+
* Optional factory method, expected to be overridden
1371+
*
13351372
* @since 2.8
13361373
*/
13371374
protected JsonParser _createParser(DataInput input, IOContext ctxt) throws IOException
13381375
{
13391376
// 13-May-2016, tatu: Need to take care not to accidentally create JSON parser for
1340-
// non-JSON input. So, bit unclean but...
1341-
String format = getFormatName();
1342-
if (format != FORMAT_NAME_JSON) { // NOTE: only ensure override; full equality NOT needed
1343-
throw new UnsupportedOperationException(String.format(
1344-
"InputData source not (yet?) support for this format (%s)", format));
1345-
}
1377+
// non-JSON input.
1378+
_requireJSONFactory("InputData source not (yet?) support for this format (%s)");
13461379
// Also: while we can't do full bootstrapping (due to read-ahead limitations), should
13471380
// at least handle possible UTF-8 BOM
13481381
int firstByte = ByteSourceJsonBootstrapper.skipUTF8BOM(input);
@@ -1558,4 +1591,31 @@ protected InputStream _optimizedStreamFromURL(URL url) throws IOException {
15581591
}
15591592
return url.openStream();
15601593
}
1594+
1595+
/*
1596+
/**********************************************************
1597+
/* Internal helper methods
1598+
/**********************************************************
1599+
*/
1600+
1601+
/**
1602+
* Helper method called to work around the problem of this class both defining
1603+
* general API for constructing parsers+generators AND implementing the API
1604+
* for JSON handling. Problem here is that when adding new functionality
1605+
* via factory methods, it is not possible to leave these methods abstract
1606+
* (because we are implementing them for JSON); but there is risk that
1607+
* sub-classes do not override them all (plus older version can not implement).
1608+
* So a work-around is to add a check to ensure that factory is still one
1609+
* used for JSON; and if not, make base implementation of a factory method fail.
1610+
*
1611+
* @since 2.9
1612+
*/
1613+
private final void _requireJSONFactory(String msg) {
1614+
// NOTE: since we only really care about whether this is standard JSON-backed factory,
1615+
// or its sub-class / delegated to one, no need to check for equality, identity is enough
1616+
String format = getFormatName();
1617+
if (format != FORMAT_NAME_JSON) {
1618+
throw new UnsupportedOperationException(String.format(msg, format));
1619+
}
1620+
}
15611621
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package com.fasterxml.jackson.core.json.async;
2+
3+
import java.io.IOException;
4+
import java.io.OutputStream;
5+
6+
import com.fasterxml.jackson.core.JsonToken;
7+
import com.fasterxml.jackson.core.async.ByteArrayFeeder;
8+
import com.fasterxml.jackson.core.async.NonBlockingInputFeeder;
9+
import com.fasterxml.jackson.core.io.IOContext;
10+
import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
11+
import com.fasterxml.jackson.core.util.VersionUtil;
12+
13+
public class NonBlockingJsonParser
14+
extends NonBlockingJsonParserBase
15+
implements ByteArrayFeeder
16+
{
17+
/*
18+
/**********************************************************************
19+
/* Input source config
20+
/**********************************************************************
21+
*/
22+
23+
/**
24+
* This buffer is actually provided via {@link NonBlockingInputFeeder}
25+
*/
26+
protected byte[] _inputBuffer = NO_BYTES;
27+
28+
/**
29+
* In addition to current buffer pointer, and end pointer,
30+
* we will also need to know number of bytes originally
31+
* contained. This is needed to correctly update location
32+
* information when the block has been completed.
33+
*/
34+
protected int _origBufferLen;
35+
36+
// And from ParserBase:
37+
// protected int _inputPtr;
38+
// protected int _inputEnd;
39+
40+
/*
41+
/**********************************************************************
42+
/* Life-cycle
43+
/**********************************************************************
44+
*/
45+
46+
public NonBlockingJsonParser(IOContext ctxt, int parserFeatures,
47+
ByteQuadsCanonicalizer sym)
48+
{
49+
super(ctxt, parserFeatures, sym);
50+
}
51+
52+
/*
53+
/**********************************************************************
54+
/* AsyncInputFeeder impl
55+
/**********************************************************************
56+
*/
57+
58+
@Override
59+
public ByteArrayFeeder getNonBlockingInputFeeder() {
60+
return this;
61+
}
62+
63+
@Override
64+
public final boolean needMoreInput() {
65+
return (_inputPtr >=_inputEnd) && !_endOfInput;
66+
}
67+
68+
@Override
69+
public void feedInput(byte[] buf, int start, int end) throws IOException
70+
{
71+
// Must not have remaining input
72+
if (_inputPtr < _inputEnd) {
73+
_reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr);
74+
}
75+
if (end < start) {
76+
_reportError("Input end (%d) may not be before start (%d)", end, start);
77+
}
78+
// and shouldn't have been marked as end-of-input
79+
if (_endOfInput) {
80+
_reportError("Already closed, can not feed more input");
81+
}
82+
// Time to update pointers first
83+
_currInputProcessed += _origBufferLen;
84+
85+
// And then update buffer settings
86+
_inputBuffer = buf;
87+
_inputPtr = start;
88+
_inputEnd = end;
89+
_origBufferLen = end - start;
90+
}
91+
92+
@Override
93+
public void endOfInput() {
94+
_endOfInput = true;
95+
}
96+
97+
/*
98+
/**********************************************************************
99+
/* Abstract methods/overrides from JsonParser
100+
/**********************************************************************
101+
*/
102+
103+
/* Implementing these methods efficiently for non-blocking cases would
104+
* be complicated; so for now let's just use the default non-optimized
105+
* implementation
106+
*/
107+
108+
// public boolean nextFieldName(SerializableString str) throws IOException
109+
// public String nextTextValue() throws IOException
110+
// public int nextIntValue(int defaultValue) throws IOException
111+
// public long nextLongValue(long defaultValue) throws IOException
112+
// public Boolean nextBooleanValue() throws IOException
113+
114+
@Override
115+
public int releaseBuffered(OutputStream out) throws IOException {
116+
int avail = _inputEnd - _inputPtr;
117+
if (avail > 0) {
118+
out.write(_inputBuffer, _inputPtr, avail);
119+
}
120+
return avail;
121+
}
122+
123+
/*
124+
/**********************************************************************
125+
/* Main-level decoding
126+
/**********************************************************************
127+
*/
128+
129+
@Override
130+
public JsonToken nextToken() throws IOException
131+
{
132+
// First: regardless of where we really are, need at least one more byte;
133+
// can simplify some of the checks by short-circuiting right away
134+
if (_inputPtr >= _inputEnd) {
135+
if (_closed) {
136+
return null;
137+
}
138+
// note: if so, do not even bother changing state
139+
if (_endOfInput) { // except for this special case
140+
return _eofAsNextToken();
141+
}
142+
return JsonToken.NOT_AVAILABLE;
143+
}
144+
// in the middle of tokenization?
145+
if (_currToken == JsonToken.NOT_AVAILABLE) {
146+
return _finishToken();
147+
}
148+
149+
// No: fresh new token; may or may not have existing one
150+
_numTypesValid = NR_UNKNOWN;
151+
// _tokenInputTotal = _currInputProcessed + _inputPtr;
152+
// also: clear any data retained so far
153+
_binaryValue = null;
154+
int ch = _inputBuffer[_inputPtr++];
155+
156+
switch (_majorState) {
157+
case MAJOR_INITIAL:
158+
// TODO: Bootstrapping? BOM?
159+
160+
case MAJOR_ROOT:
161+
return _startValue(ch);
162+
163+
case MAJOR_OBJECT_FIELD: // field or end-object
164+
// expect name
165+
return _startFieldName(ch);
166+
167+
case MAJOR_OBJECT_VALUE:
168+
case MAJOR_ARRAY_ELEMENT: // element or end-array
169+
return _startValue(ch);
170+
171+
default:
172+
}
173+
VersionUtil.throwInternal();
174+
return null;
175+
}
176+
177+
/**
178+
* Method called when a (scalar) value type has been detected, but not all of
179+
* contents have been decoded due to incomplete input available.
180+
*/
181+
protected final JsonToken _finishToken() throws IOException
182+
{
183+
// NOTE: caller ensures availability of at least one byte
184+
185+
switch (_minorState) {
186+
}
187+
return null;
188+
}
189+
190+
/*
191+
/**********************************************************************
192+
/* Second-level decoding, root level
193+
/**********************************************************************
194+
*/
195+
196+
/**
197+
* Helper method called to detect type of a value token (at any level), and possibly
198+
* decode it if contained in input buffer.
199+
* Note that possible header has been ruled out by caller and is not checked here.
200+
*/
201+
private final JsonToken _startValue(int ch) throws IOException
202+
{
203+
return null;
204+
}
205+
206+
/*
207+
/**********************************************************************
208+
/* Second-level decoding, Name decoding
209+
/**********************************************************************
210+
*/
211+
212+
/**
213+
* Method that handles initial token type recognition for token
214+
* that has to be either FIELD_NAME or END_OBJECT.
215+
*/
216+
protected final JsonToken _startFieldName(int ch) throws IOException
217+
{
218+
return null;
219+
}
220+
}

0 commit comments

Comments
 (0)