blob: d53bdd98aca72b1021f6cdcfc5a28179e5830417 [file] [log] [blame]
Tatu Saloranta81fb43c2017-05-17 18:25:34 -07001package com.fasterxml.jackson.core.json.async;
2
3import java.io.IOException;
4import java.io.OutputStream;
5
6import com.fasterxml.jackson.core.JsonToken;
7import com.fasterxml.jackson.core.async.ByteArrayFeeder;
8import com.fasterxml.jackson.core.async.NonBlockingInputFeeder;
9import com.fasterxml.jackson.core.io.IOContext;
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -070010import com.fasterxml.jackson.core.json.ByteSourceJsonBootstrapper;
Tatu Saloranta81fb43c2017-05-17 18:25:34 -070011import com.fasterxml.jackson.core.sym.ByteQuadsCanonicalizer;
12import com.fasterxml.jackson.core.util.VersionUtil;
13
14public class NonBlockingJsonParser
15 extends NonBlockingJsonParserBase
16 implements ByteArrayFeeder
17{
18 /*
19 /**********************************************************************
20 /* Input source config
21 /**********************************************************************
22 */
23
24 /**
25 * This buffer is actually provided via {@link NonBlockingInputFeeder}
26 */
27 protected byte[] _inputBuffer = NO_BYTES;
28
29 /**
30 * In addition to current buffer pointer, and end pointer,
31 * we will also need to know number of bytes originally
32 * contained. This is needed to correctly update location
33 * information when the block has been completed.
34 */
35 protected int _origBufferLen;
36
37 // And from ParserBase:
38// protected int _inputPtr;
39// protected int _inputEnd;
40
41 /*
42 /**********************************************************************
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -070043 /* Location tracking, additional
44 /**********************************************************************
45 */
46
47 /**
48 * Alternate row tracker, used to keep track of position by `\r` marker
49 * (whereas <code>_currInputRow</code> tracks `\n`). Used to simplify
50 * tracking of linefeeds, assuming that input typically uses various
51 * linefeed combinations (`\r`, `\n` or `\r\n`) consistently, in which
52 * case we can simply choose max of two row candidates.
53 */
54 protected int _currInputRowAlt = 1;
55
56 /*
57 /**********************************************************************
58 /* Other state
59 /**********************************************************************
60 */
61
62 protected int _currentQuote;
63
64 /*
65 /**********************************************************************
Tatu Saloranta81fb43c2017-05-17 18:25:34 -070066 /* Life-cycle
67 /**********************************************************************
68 */
69
70 public NonBlockingJsonParser(IOContext ctxt, int parserFeatures,
71 ByteQuadsCanonicalizer sym)
72 {
73 super(ctxt, parserFeatures, sym);
74 }
75
76 /*
77 /**********************************************************************
78 /* AsyncInputFeeder impl
79 /**********************************************************************
80 */
81
82 @Override
83 public ByteArrayFeeder getNonBlockingInputFeeder() {
84 return this;
85 }
86
87 @Override
88 public final boolean needMoreInput() {
89 return (_inputPtr >=_inputEnd) && !_endOfInput;
90 }
91
92 @Override
93 public void feedInput(byte[] buf, int start, int end) throws IOException
94 {
95 // Must not have remaining input
96 if (_inputPtr < _inputEnd) {
97 _reportError("Still have %d undecoded bytes, should not call 'feedInput'", _inputEnd - _inputPtr);
98 }
99 if (end < start) {
100 _reportError("Input end (%d) may not be before start (%d)", end, start);
101 }
102 // and shouldn't have been marked as end-of-input
103 if (_endOfInput) {
104 _reportError("Already closed, can not feed more input");
105 }
106 // Time to update pointers first
107 _currInputProcessed += _origBufferLen;
108
109 // And then update buffer settings
110 _inputBuffer = buf;
111 _inputPtr = start;
112 _inputEnd = end;
113 _origBufferLen = end - start;
114 }
115
116 @Override
117 public void endOfInput() {
118 _endOfInput = true;
119 }
120
121 /*
122 /**********************************************************************
123 /* Abstract methods/overrides from JsonParser
124 /**********************************************************************
125 */
126
127 /* Implementing these methods efficiently for non-blocking cases would
128 * be complicated; so for now let's just use the default non-optimized
129 * implementation
130 */
131
132// public boolean nextFieldName(SerializableString str) throws IOException
133// public String nextTextValue() throws IOException
134// public int nextIntValue(int defaultValue) throws IOException
135// public long nextLongValue(long defaultValue) throws IOException
136// public Boolean nextBooleanValue() throws IOException
137
138 @Override
139 public int releaseBuffered(OutputStream out) throws IOException {
140 int avail = _inputEnd - _inputPtr;
141 if (avail > 0) {
142 out.write(_inputBuffer, _inputPtr, avail);
143 }
144 return avail;
145 }
146
147 /*
148 /**********************************************************************
149 /* Main-level decoding
150 /**********************************************************************
151 */
152
153 @Override
154 public JsonToken nextToken() throws IOException
155 {
156 // First: regardless of where we really are, need at least one more byte;
157 // can simplify some of the checks by short-circuiting right away
158 if (_inputPtr >= _inputEnd) {
159 if (_closed) {
160 return null;
161 }
162 // note: if so, do not even bother changing state
163 if (_endOfInput) { // except for this special case
164 return _eofAsNextToken();
165 }
166 return JsonToken.NOT_AVAILABLE;
167 }
168 // in the middle of tokenization?
169 if (_currToken == JsonToken.NOT_AVAILABLE) {
170 return _finishToken();
171 }
172
173 // No: fresh new token; may or may not have existing one
174 _numTypesValid = NR_UNKNOWN;
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -0700175 _tokenInputTotal = _currInputProcessed + _inputPtr;
Tatu Saloranta81fb43c2017-05-17 18:25:34 -0700176 // also: clear any data retained so far
177 _binaryValue = null;
178 int ch = _inputBuffer[_inputPtr++];
179
180 switch (_majorState) {
181 case MAJOR_INITIAL:
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -0700182 return _startDocument(ch);
Tatu Saloranta81fb43c2017-05-17 18:25:34 -0700183
184 case MAJOR_ROOT:
185 return _startValue(ch);
186
187 case MAJOR_OBJECT_FIELD: // field or end-object
188 // expect name
189 return _startFieldName(ch);
190
191 case MAJOR_OBJECT_VALUE:
192 case MAJOR_ARRAY_ELEMENT: // element or end-array
193 return _startValue(ch);
194
195 default:
196 }
197 VersionUtil.throwInternal();
198 return null;
199 }
200
201 /**
202 * Method called when a (scalar) value type has been detected, but not all of
203 * contents have been decoded due to incomplete input available.
204 */
205 protected final JsonToken _finishToken() throws IOException
206 {
207 // NOTE: caller ensures availability of at least one byte
208
209 switch (_minorState) {
210 }
211 return null;
212 }
213
214 /*
215 /**********************************************************************
216 /* Second-level decoding, root level
217 /**********************************************************************
218 */
219
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -0700220 private final JsonToken _startDocument(int ch) throws IOException
221 {
222 ch &= 0xFF;
223
224 // Very first byte: could be BOM
225 if (ch == ByteSourceJsonBootstrapper.UTF8_BOM_1) {
226 // !!! TODO
227 }
228
229 // If not BOM (or we got past it), could be whitespace or comment to skip
230 while (ch <= 0x020) {
231 if (ch != INT_SPACE) {
232 if (ch == INT_LF) {
233 ++_currInputRow;
234 _currInputRowStart = _inputPtr;
235 } else if (ch == INT_CR) {
236 ++_currInputRowAlt;
237 _currInputRowStart = _inputPtr;
238 } else if (ch != INT_TAB) {
239 _throwInvalidSpace(ch);
240 }
241 }
242 if (_inputPtr >= _inputEnd) {
243 _minorState = MINOR_FIELD_ROOT_GOT_SEPARATOR;
244 if (_closed) {
245 return null;
246 }
247 // note: if so, do not even bother changing state
248 if (_endOfInput) { // except for this special case
249 return _eofAsNextToken();
250 }
251 return JsonToken.NOT_AVAILABLE;
252 }
253 ch = _inputBuffer[_inputPtr++] & 0xFF;
254 }
255 return _startValue(ch);
256 }
257
258 /*
259 /**********************************************************************
260 /* Second-level decoding, value parsing
261 /**********************************************************************
262 */
263
Tatu Saloranta81fb43c2017-05-17 18:25:34 -0700264 /**
265 * Helper method called to detect type of a value token (at any level), and possibly
266 * decode it if contained in input buffer.
267 * Note that possible header has been ruled out by caller and is not checked here.
268 */
269 private final JsonToken _startValue(int ch) throws IOException
270 {
Tatu Saloranta8e8ed3e2017-05-19 17:07:48 -0700271 if (ch == INT_QUOTE) {
272 return _startString(ch);
273 }
274 switch (ch) {
275 case '-':
276 return _startNegativeNumber();
277
278 // Should we have separate handling for plus? Although
279 // it is not allowed per se, it may be erroneously used,
280 // and could be indicate by a more specific error message.
281 case '0':
282 case '1':
283 case '2':
284 case '3':
285 case '4':
286 case '5':
287 case '6':
288 case '7':
289 case '8':
290 case '9':
291 return _startPositiveNumber(ch);
292 case 'f':
293 return _startFalseToken();
294 case 'n':
295 return _startNullToken();
296 case 't':
297 return _startTrueToken();
298 case '[':
299 return _startArrayScope();
300 case ']':
301 return _closeArrayScope();
302 case '{':
303 return _startObjectScope();
304 case '}':
305 return _closeObjectScope();
306 default:
307 }
308 return _startUnexpectedValue(ch);
309 }
310
311 protected JsonToken _startUnexpectedValue(int ch) throws IOException
312 {
313 // TODO: Maybe support non-standard tokens that streaming parser does:
314 //
315 // * NaN
316 // * Infinity
317 // * Plus-prefix for numbers
318 // * Apostrophe for Strings
319
320 switch (ch) {
321 case '\'':
322 return _startString(ch);
323
324 case ',':
325 // If Feature.ALLOW_MISSING_VALUES is enabled we may allow "missing values",
326 // that is, encountering a trailing comma or closing marker where value would be expected
327 if (!_parsingContext.inObject() && isEnabled(Feature.ALLOW_MISSING_VALUES)) {
328 // Important to "push back" separator, to be consumed before next value;
329 // does not lead to infinite loop
330 --_inputPtr;
331 return _valueComplete(JsonToken.VALUE_NULL);
332 }
333 break;
334 }
335 // !!! TODO: maybe try to collect more information for better diagnostics
336 _reportUnexpectedChar(ch, "expected a valid value (number, String, array, object, 'true', 'false' or 'null')");
337 return null;
338 }
339
340 /*
341 /**********************************************************************
342 /* Second-level decoding, simple tokens
343 /**********************************************************************
344 */
345
346 protected JsonToken _startFalseToken() throws IOException
347 {
348 return null;
349 }
350
351 protected JsonToken _startTrueToken() throws IOException
352 {
353 return null;
354 }
355
356 protected JsonToken _startNullToken() throws IOException
357 {
358 return null;
359 }
360
361 /*
362 /**********************************************************************
363 /* Second-level decoding, String decoding
364 /**********************************************************************
365 */
366
367 protected JsonToken _startString(int q) throws IOException
368 {
369 _currentQuote = q;
370 return null;
371 }
372
373 /*
374 /**********************************************************************
375 /* Second-level decoding, String decoding
376 /**********************************************************************
377 */
378
379 protected JsonToken _startPositiveNumber(int ch) throws IOException
380 {
381 return null;
382 }
383
384 protected JsonToken _startNegativeNumber() throws IOException
385 {
Tatu Saloranta81fb43c2017-05-17 18:25:34 -0700386 return null;
387 }
388
389 /*
390 /**********************************************************************
391 /* Second-level decoding, Name decoding
392 /**********************************************************************
393 */
394
395 /**
396 * Method that handles initial token type recognition for token
397 * that has to be either FIELD_NAME or END_OBJECT.
398 */
399 protected final JsonToken _startFieldName(int ch) throws IOException
400 {
401 return null;
402 }
403}