1 package com.fasterxml.jackson.core.json;
2
3 import java.io.*;
4
5 import com.fasterxml.jackson.core.*;
6 import com.fasterxml.jackson.core.base.ParserBase;
7 import com.fasterxml.jackson.core.io.CharTypes;
8 import com.fasterxml.jackson.core.io.IOContext;
9 import com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer;
10 import com.fasterxml.jackson.core.util.*;
11
12 import static com.fasterxml.jackson.core.JsonTokenId.*;
13
14 /**
15  * This is a concrete implementation of {@link JsonParser}, which is
16  * based on a {@link java.io.Reader} to handle low-level character
17  * conversion tasks.
18  */

19 public class ReaderBasedJsonParser // final in 2.3, earlier
20     extends ParserBase
21 {
22     @SuppressWarnings("deprecation")
23     private final static int FEAT_MASK_TRAILING_COMMA = Feature.ALLOW_TRAILING_COMMA.getMask();
24
25     @SuppressWarnings("deprecation")
26     private final static int FEAT_MASK_LEADING_ZEROS = Feature.ALLOW_NUMERIC_LEADING_ZEROS.getMask();
27
28     @SuppressWarnings("deprecation")
29     private final static int FEAT_MASK_NON_NUM_NUMBERS = Feature.ALLOW_NON_NUMERIC_NUMBERS.getMask();
30
31     @SuppressWarnings("deprecation")
32     private final static int FEAT_MASK_ALLOW_MISSING = Feature.ALLOW_MISSING_VALUES.getMask();
33     private final static int FEAT_MASK_ALLOW_SINGLE_QUOTES = Feature.ALLOW_SINGLE_QUOTES.getMask();
34     private final static int FEAT_MASK_ALLOW_UNQUOTED_NAMES = Feature.ALLOW_UNQUOTED_FIELD_NAMES.getMask();
35
36     private final static int FEAT_MASK_ALLOW_JAVA_COMMENTS = Feature.ALLOW_COMMENTS.getMask();
37     private final static int FEAT_MASK_ALLOW_YAML_COMMENTS = Feature.ALLOW_YAML_COMMENTS.getMask();
38
39     // Latin1 encoding is not supported, but we do use 8-bit subset for
40     // pre-processing task, to simplify first pass, keep it fast.
41     protected final static int[] _icLatin1 = CharTypes.getInputCodeLatin1();
42
43     /*
44     /**********************************************************
45     /* Input configuration
46     /**********************************************************
47      */

48
49     /**
50      * Reader that can be used for reading more content, if one
51      * buffer from input source, but in some cases pre-loaded buffer
52      * is handed to the parser.
53      */

54     protected Reader _reader;
55
56     /**
57      * Current buffer from which data is read; generally data is read into
58      * buffer from input source.
59      */

60     protected char[] _inputBuffer;
61
62     /**
63      * Flag that indicates whether the input buffer is recycable (and
64      * needs to be returned to recycler once we are done) or not.
65      *<p>
66      * If it is not, it also means that parser can NOT modify underlying
67      * buffer.
68      */

69     protected boolean _bufferRecyclable;
70
71     /*
72     /**********************************************************
73     /* Configuration
74     /**********************************************************
75      */

76
77     protected ObjectCodec _objectCodec;
78
79     final protected CharsToNameCanonicalizer _symbols;
80
81     final protected int _hashSeed;
82
83     /*
84     /**********************************************************
85     /* Parsing state
86     /**********************************************************
87      */

88
89     /**
90      * Flag that indicates that the current token has not yet
91      * been fully processed, and needs to be finished for
92      * some access (or skipped to obtain the next token)
93      */

94     protected boolean _tokenIncomplete;
95
96     /**
97      * Value of {@link #_inputPtr} at the time when the first character of
98      * name token was read. Used for calculating token location when requested;
99      * combined with {@link #_currInputProcessed}, may be updated appropriately
100      * as needed.
101      *
102      * @since 2.7
103      */

104     protected long _nameStartOffset;
105
106     /**
107      * @since 2.7
108      */

109     protected int _nameStartRow;
110
111     /**
112      * @since 2.7
113      */

114     protected int _nameStartCol;
115
116     /*
117     /**********************************************************
118     /* Life-cycle
119     /**********************************************************
120      */

121
122     /**
123      * Method called when caller wants to provide input buffer directly,
124      * and it may or may not be recyclable use standard recycle context.
125      *
126      * @since 2.4
127      */

128     public ReaderBasedJsonParser(IOContext ctxt, int features, Reader r,
129             ObjectCodec codec, CharsToNameCanonicalizer st,
130             char[] inputBuffer, int start, int end,
131             boolean bufferRecyclable)
132     {
133         super(ctxt, features);
134         _reader = r;
135         _inputBuffer = inputBuffer;
136         _inputPtr = start;
137         _inputEnd = end;
138         _objectCodec = codec;
139         _symbols = st;
140         _hashSeed = st.hashSeed();
141         _bufferRecyclable = bufferRecyclable;
142     }
143
144     /**
145      * Method called when input comes as a {@link java.io.Reader}, and buffer allocation
146      * can be done using default mechanism.
147      */

148     public ReaderBasedJsonParser(IOContext ctxt, int features, Reader r,
149         ObjectCodec codec, CharsToNameCanonicalizer st)
150     {
151         super(ctxt, features);
152         _reader = r;
153         _inputBuffer = ctxt.allocTokenBuffer();
154         _inputPtr = 0;
155         _inputEnd = 0;
156         _objectCodec = codec;
157         _symbols = st;
158         _hashSeed = st.hashSeed();
159         _bufferRecyclable = true;
160     }
161
162     /*
163     /**********************************************************
164     /* Base method defs, overrides
165     /**********************************************************
166      */

167
168     @Override public ObjectCodec getCodec() { return _objectCodec; }
169     @Override public void setCodec(ObjectCodec c) { _objectCodec = c; }
170
171     @Override
172     public int releaseBuffered(Writer w) throws IOException {
173         int count = _inputEnd - _inputPtr;
174         if (count < 1) { return 0; }
175         // let's just advance ptr to end
176         int origPtr = _inputPtr;
177         _inputPtr += count;
178         w.write(_inputBuffer, origPtr, count);
179         return count;
180     }
181
182     @Override public Object getInputSource() { return _reader; }
183
184     @Deprecated // since 2.8
185     protected char getNextChar(String eofMsg) throws IOException {
186         return getNextChar(eofMsg, null);
187     }
188     
189     protected char getNextChar(String eofMsg, JsonToken forToken) throws IOException {
190         if (_inputPtr >= _inputEnd) {
191             if (!_loadMore()) {
192                 _reportInvalidEOF(eofMsg, forToken);
193             }
194         }
195         return _inputBuffer[_inputPtr++];
196     }
197
198     @Override
199     protected void _closeInput() throws IOException {
200         /* 25-Nov-2008, tatus: As per [JACKSON-16] we are not to call close()
201          *   on the underlying Reader, unless we "own" it, or auto-closing
202          *   feature is enabled.
203          *   One downside is that when using our optimized
204          *   Reader (granted, we only do that for UTF-32...) this
205          *   means that buffer recycling won't work correctly.
206          */

207         if (_reader != null) {
208             if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_SOURCE)) {
209                 _reader.close();
210             }
211             _reader = null;
212         }
213     }
214
215     /**
216      * Method called to release internal buffers owned by the base
217      * reader. This may be called along with {@link #_closeInput} (for
218      * example, when explicitly closing this reader instance), or
219      * separately (if need be).
220      */

221     @Override
222     protected void _releaseBuffers() throws IOException
223     {
224         super._releaseBuffers();
225         // merge new symbols, if any
226         _symbols.release();
227         // and release buffers, if they are recyclable ones
228         if (_bufferRecyclable) {
229             char[] buf = _inputBuffer;
230             if (buf != null) {
231                 _inputBuffer = null;
232                 _ioContext.releaseTokenBuffer(buf);
233             }
234         }
235     }
236
237     /*
238     /**********************************************************
239     /* Low-level access, supporting
240     /**********************************************************
241      */

242
243     protected void _loadMoreGuaranteed() throws IOException {
244         if (!_loadMore()) { _reportInvalidEOF(); }
245     }
246     
247     protected boolean _loadMore() throws IOException
248     {
249         if (_reader != null) {
250             int count = _reader.read(_inputBuffer, 0, _inputBuffer.length);
251             if (count > 0) {
252                 final int bufSize = _inputEnd;
253                 _currInputProcessed += bufSize;
254                 _currInputRowStart -= bufSize;
255
256                 // 26-Nov-2015, tatu: Since name-offset requires it too, must offset
257                 //   this increase to avoid "moving" name-offset, resulting most likely
258                 //   in negative value, which is fine as combine value remains unchanged.
259                 _nameStartOffset -= bufSize;
260
261                 _inputPtr = 0;
262                 _inputEnd = count;
263
264                 return true;
265             }
266             // End of input
267             _closeInput();
268             // Should never return 0, so let's fail
269             if (count == 0) {
270                 throw new IOException("Reader returned 0 characters when trying to read "+_inputEnd);
271             }
272         }
273         return false;
274     }
275
276     /*
277     /**********************************************************
278     /* Public API, data access
279     /**********************************************************
280      */

281
282     /**
283      * Method for accessing textual representation of the current event;
284      * if no current event (before first call to {@link #nextToken}, or
285      * after encountering end-of-input), returns null.
286      * Method can be called for any event.
287      */

288     @Override
289     public final String getText() throws IOException
290     {
291         if (_currToken == JsonToken.VALUE_STRING) {
292             if (_tokenIncomplete) {
293                 _tokenIncomplete = false;
294                 _finishString(); // only strings can be incomplete
295             }
296             return _textBuffer.contentsAsString();
297         }
298         return _getText2(_currToken);
299     }
300
301     @Override // since 2.8
302     public int getText(Writer writer) throws IOException
303     {
304         JsonToken t = _currToken;
305         if (t == JsonToken.VALUE_STRING) {
306             if (_tokenIncomplete) {
307                 _tokenIncomplete = false;
308                 _finishString(); // only strings can be incomplete
309             }
310             return _textBuffer.contentsToWriter(writer);
311         }
312         if (t == JsonToken.FIELD_NAME) {
313             String n = _parsingContext.getCurrentName();
314             writer.write(n);
315             return n.length();
316         }
317         if (t != null) {
318             if (t.isNumeric()) {
319                 return _textBuffer.contentsToWriter(writer);
320             }
321             char[] ch = t.asCharArray();
322             writer.write(ch);
323             return ch.length;
324         }
325         return 0;
326     }
327     
328     // // // Let's override default impls for improved performance
329
330     // @since 2.1
331     @Override
332     public final String getValueAsString() throws IOException
333     {
334         if (_currToken == JsonToken.VALUE_STRING) {
335             if (_tokenIncomplete) {
336                 _tokenIncomplete = false;
337                 _finishString(); // only strings can be incomplete
338             }
339             return _textBuffer.contentsAsString();
340         }
341         if (_currToken == JsonToken.FIELD_NAME) {
342             return getCurrentName();
343         }
344         return super.getValueAsString(null);
345     }
346
347     // @since 2.1
348     @Override
349     public final String getValueAsString(String defValue) throws IOException {
350         if (_currToken == JsonToken.VALUE_STRING) {
351             if (_tokenIncomplete) {
352                 _tokenIncomplete = false;
353                 _finishString(); // only strings can be incomplete
354             }
355             return _textBuffer.contentsAsString();
356         }
357         if (_currToken == JsonToken.FIELD_NAME) {
358             return getCurrentName();
359         }
360         return super.getValueAsString(defValue);
361     }
362
363     protected final String _getText2(JsonToken t) {
364         if (t == null) {
365             return null;
366         }
367         switch (t.id()) {
368         case ID_FIELD_NAME:
369             return _parsingContext.getCurrentName();
370
371         case ID_STRING:
372             // fall through
373         case ID_NUMBER_INT:
374         case ID_NUMBER_FLOAT:
375             return _textBuffer.contentsAsString();
376         default:
377             return t.asString();
378         }
379     }
380
381     @Override
382     public final char[] getTextCharacters() throws IOException
383     {
384         if (_currToken != null) { // null only before/after document
385             switch (_currToken.id()) {
386             case ID_FIELD_NAME:
387                 if (!_nameCopied) {
388                     String name = _parsingContext.getCurrentName();
389                     int nameLen = name.length();
390                     if (_nameCopyBuffer == null) {
391                         _nameCopyBuffer = _ioContext.allocNameCopyBuffer(nameLen);
392                     } else if (_nameCopyBuffer.length < nameLen) {
393                         _nameCopyBuffer = new char[nameLen];
394                     }
395                     name.getChars(0, nameLen, _nameCopyBuffer, 0);
396                     _nameCopied = true;
397                 }
398                 return _nameCopyBuffer;
399             case ID_STRING:
400                 if (_tokenIncomplete) {
401                     _tokenIncomplete = false;
402                     _finishString(); // only strings can be incomplete
403                 }
404                 // fall through
405             case ID_NUMBER_INT:
406             case ID_NUMBER_FLOAT:
407                 return _textBuffer.getTextBuffer();
408             default:
409                 return _currToken.asCharArray();
410             }
411         }
412         return null;
413     }
414
415     @Override
416     public final int getTextLength() throws IOException
417     {
418         if (_currToken != null) { // null only before/after document
419             switch (_currToken.id()) {
420             case ID_FIELD_NAME:
421                 return _parsingContext.getCurrentName().length();
422             case ID_STRING:
423                 if (_tokenIncomplete) {
424                     _tokenIncomplete = false;
425                     _finishString(); // only strings can be incomplete
426                 }
427                 // fall through
428             case ID_NUMBER_INT:
429             case ID_NUMBER_FLOAT:
430                 return _textBuffer.size();
431             default:
432                 return _currToken.asCharArray().length;
433             }
434         }
435         return 0;
436     }
437
438     @Override
439     public final int getTextOffset() throws IOException
440     {
441         // Most have offset of 0, only some may have other values:
442         if (_currToken != null) {
443             switch (_currToken.id()) {
444             case ID_FIELD_NAME:
445                 return 0;
446             case ID_STRING:
447                 if (_tokenIncomplete) {
448                     _tokenIncomplete = false;
449                     _finishString(); // only strings can be incomplete
450                 }
451                 // fall through
452             case ID_NUMBER_INT:
453             case ID_NUMBER_FLOAT:
454                 return _textBuffer.getTextOffset();
455             default:
456             }
457         }
458         return 0;
459     }
460
461     @Override
462     public byte[] getBinaryValue(Base64Variant b64variant) throws IOException
463     {
464         if ((_currToken == JsonToken.VALUE_EMBEDDED_OBJECT) && (_binaryValue != null)) {
465             return _binaryValue;
466         }
467         if (_currToken != JsonToken.VALUE_STRING) {
468             _reportError("Current token ("+_currToken+") not VALUE_STRING or VALUE_EMBEDDED_OBJECT, can not access as binary");
469         }
470         // To ensure that we won't see inconsistent data, better clear up state
471         if (_tokenIncomplete) {
472             try {
473                 _binaryValue = _decodeBase64(b64variant);
474             } catch (IllegalArgumentException iae) {
475                 throw _constructError("Failed to decode VALUE_STRING as base64 ("+b64variant+"): "+iae.getMessage());
476             }
477             /* let's clear incomplete only now; allows for accessing other
478              * textual content in error cases
479              */

480             _tokenIncomplete = false;
481         } else { // may actually require conversion...
482             if (_binaryValue == null) {
483                 @SuppressWarnings("resource")
484                 ByteArrayBuilder builder = _getByteArrayBuilder();
485                 _decodeBase64(getText(), builder, b64variant);
486                 _binaryValue = builder.toByteArray();
487             }
488         }
489         return _binaryValue;
490     }
491
492     @Override
493     public int readBinaryValue(Base64Variant b64variant, OutputStream out) throws IOException
494     {
495         // if we have already read the token, just use whatever we may have
496         if (!_tokenIncomplete || _currToken != JsonToken.VALUE_STRING) {
497             byte[] b = getBinaryValue(b64variant);
498             out.write(b);
499             return b.length;
500         }
501         // otherwise do "real" incremental parsing...
502         byte[] buf = _ioContext.allocBase64Buffer();
503         try {
504             return _readBinary(b64variant, out, buf);
505         } finally {
506             _ioContext.releaseBase64Buffer(buf);
507         }
508     }
509
510     protected int _readBinary(Base64Variant b64variant, OutputStream out, byte[] buffer) throws IOException
511     {
512         int outputPtr = 0;
513         final int outputEnd = buffer.length - 3;
514         int outputCount = 0;
515
516         while (true) {
517             // first, we'll skip preceding white space, if any
518             char ch;
519             do {
520                 if (_inputPtr >= _inputEnd) {
521                     _loadMoreGuaranteed();
522                 }
523                 ch = _inputBuffer[_inputPtr++];
524             } while (ch <= INT_SPACE);
525             int bits = b64variant.decodeBase64Char(ch);
526             if (bits < 0) { // reached the end, fair and square?
527                 if (ch == '"') {
528                     break;
529                 }
530                 bits = _decodeBase64Escape(b64variant, ch, 0);
531                 if (bits < 0) { // white space to skip
532                     continue;
533                 }
534             }
535
536             // enough room? If not, flush
537             if (outputPtr > outputEnd) {
538                 outputCount += outputPtr;
539                 out.write(buffer, 0, outputPtr);
540                 outputPtr = 0;
541             }
542
543             int decodedData = bits;
544
545             // then second base64 char; can't get padding yet, nor ws
546
547             if (_inputPtr >= _inputEnd) {
548                 _loadMoreGuaranteed();
549             }
550             ch = _inputBuffer[_inputPtr++];
551             bits = b64variant.decodeBase64Char(ch);
552             if (bits < 0) {
553                 bits = _decodeBase64Escape(b64variant, ch, 1);
554             }
555             decodedData = (decodedData << 6) | bits;
556
557             // third base64 char; can be padding, but not ws
558             if (_inputPtr >= _inputEnd) {
559                 _loadMoreGuaranteed();
560             }
561             ch = _inputBuffer[_inputPtr++];
562             bits = b64variant.decodeBase64Char(ch);
563
564             // First branch: can get padding (-> 1 byte)
565             if (bits < 0) {
566                 if (bits != Base64Variant.BASE64_VALUE_PADDING) {
567                     // as per [JACKSON-631], could also just be 'missing'  padding
568                     if (ch == '"') {
569                         decodedData >>= 4;
570                         buffer[outputPtr++] = (byte) decodedData;
571                         if (b64variant.usesPadding()) {
572                             --_inputPtr; // to keep parser state bit more consistent
573                             _handleBase64MissingPadding(b64variant);
574                         }
575                         break;
576                     }
577                     bits = _decodeBase64Escape(b64variant, ch, 2);
578                 }
579                 if (bits == Base64Variant.BASE64_VALUE_PADDING) {
580                     // Ok, must get padding
581                     if (_inputPtr >= _inputEnd) {
582                         _loadMoreGuaranteed();
583                     }
584                     ch = _inputBuffer[_inputPtr++];
585                     if (!b64variant.usesPaddingChar(ch)) {
586                         if (_decodeBase64Escape(b64variant, ch, 3) != Base64Variant.BASE64_VALUE_PADDING) {
587                             throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
588                         }
589                     }
590                     // Got 12 bits, only need 8, need to shift
591                     decodedData >>= 4;
592                     buffer[outputPtr++] = (byte) decodedData;
593                     continue;
594                 }
595             }
596             // Nope, 2 or 3 bytes
597             decodedData = (decodedData << 6) | bits;
598             // fourth and last base64 char; can be padding, but not ws
599             if (_inputPtr >= _inputEnd) {
600                 _loadMoreGuaranteed();
601             }
602             ch = _inputBuffer[_inputPtr++];
603             bits = b64variant.decodeBase64Char(ch);
604             if (bits < 0) {
605                 if (bits != Base64Variant.BASE64_VALUE_PADDING) {
606                     // as per [JACKSON-631], could also just be 'missing'  padding
607                     if (ch == '"') {
608                         decodedData >>= 2;
609                         buffer[outputPtr++] = (byte) (decodedData >> 8);
610                         buffer[outputPtr++] = (byte) decodedData;
611                         if (b64variant.usesPadding()) {
612                             --_inputPtr; // to keep parser state bit more consistent
613                             _handleBase64MissingPadding(b64variant);
614                         }
615                         break;
616                     }
617                     bits = _decodeBase64Escape(b64variant, ch, 3);
618                 }
619                 if (bits == Base64Variant.BASE64_VALUE_PADDING) {
620                     /* With padding we only get 2 bytes; but we have
621                      * to shift it a bit so it is identical to triplet
622                      * case with partial output.
623                      * 3 chars gives 3x6 == 18 bits, of which 2 are
624                      * dummies, need to discard:
625                      */

626                     decodedData >>= 2;
627                     buffer[outputPtr++] = (byte) (decodedData >> 8);
628                     buffer[outputPtr++] = (byte) decodedData;
629                     continue;
630                 }
631             }
632             // otherwise, our triplet is now complete
633             decodedData = (decodedData << 6) | bits;
634             buffer[outputPtr++] = (byte) (decodedData >> 16);
635             buffer[outputPtr++] = (byte) (decodedData >> 8);
636             buffer[outputPtr++] = (byte) decodedData;
637         }
638         _tokenIncomplete = false;
639         if (outputPtr > 0) {
640             outputCount += outputPtr;
641             out.write(buffer, 0, outputPtr);
642         }
643         return outputCount;
644     }
645
646     /*
647     /**********************************************************
648     /* Public API, traversal
649     /**********************************************************
650      */

651
652     /**
653      * @return Next token from the stream, if any found, or null
654      *   to indicate end-of-input
655      */

656     @Override
657     public final JsonToken nextToken() throws IOException
658     {
659         /* First: field names are special -- we will always tokenize
660          * (part of) value along with field name to simplify
661          * state handling. If so, can and need to use secondary token:
662          */

663         if (_currToken == JsonToken.FIELD_NAME) {
664             return _nextAfterName();
665         }
666         // But if we didn't already have a name, and (partially?) decode number,
667         // need to ensure no numeric information is leaked
668         _numTypesValid = NR_UNKNOWN;
669         if (_tokenIncomplete) {
670             _skipString(); // only strings can be partial
671         }
672         int i = _skipWSOrEnd();
673         if (i < 0) { // end-of-input
674             // Should actually close/release things
675             // like input source, symbol table and recyclable buffers now.
676             close();
677             return (_currToken = null);
678         }
679         // clear any data retained so far
680         _binaryValue = null;
681
682         // Closing scope?
683         if (i == INT_RBRACKET || i == INT_RCURLY) {
684             _closeScope(i);
685             return _currToken;
686         }
687
688         // Nope: do we then expect a comma?
689         if (_parsingContext.expectComma()) {
690             i = _skipComma(i);
691
692             // Was that a trailing comma?
693             if ((_features & FEAT_MASK_TRAILING_COMMA) != 0) {
694                 if ((i == INT_RBRACKET) || (i == INT_RCURLY)) {
695                     _closeScope(i);
696                     return _currToken;
697                 }
698             }
699         }
700
701         /* And should we now have a name? Always true for Object contexts, since
702          * the intermediate 'expect-value' state is never retained.
703          */

704         boolean inObject = _parsingContext.inObject();
705         if (inObject) {
706             // First, field name itself:
707             _updateNameLocation();
708             String name = (i == INT_QUOTE) ? _parseName() : _handleOddName(i);
709             _parsingContext.setCurrentName(name);
710             _currToken = JsonToken.FIELD_NAME;
711             i = _skipColon();
712         }
713         _updateLocation();
714
715         // Ok: we must have a value... what is it?
716
717         JsonToken t;
718
719         switch (i) {
720         case '"':
721             _tokenIncomplete = true;
722             t = JsonToken.VALUE_STRING;
723             break;
724         case '[':
725             if (!inObject) {
726                 _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
727             }
728             t = JsonToken.START_ARRAY;
729             break;
730         case '{':
731             if (!inObject) {
732                 _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
733             }
734             t = JsonToken.START_OBJECT;
735             break;
736         case '}':
737             // Error: } is not valid at this point; valid closers have
738             // been handled earlier
739             _reportUnexpectedChar(i, "expected a value");
740         case 't':
741             _matchTrue();
742             t = JsonToken.VALUE_TRUE;
743             break;
744         case 'f':
745             _matchFalse();
746             t = JsonToken.VALUE_FALSE;
747             break;
748         case 'n':
749             _matchNull();
750             t = JsonToken.VALUE_NULL;
751             break;
752
753         case '-':
754             /* Should we have separate handling for plus? Although
755              * it is not allowed per se, it may be erroneously used,
756              * and could be indicate by a more specific error message.
757              */

758             t = _parseNegNumber();
759             break;
760         case '.': // [core#61]]
761             t = _parseFloatThatStartsWithPeriod();
762             break;
763         case '0':
764         case '1':
765         case '2':
766         case '3':
767         case '4':
768         case '5':
769         case '6':
770         case '7':
771         case '8':
772         case '9':
773             t = _parsePosNumber(i);
774             break;
775         default:
776             t = _handleOddValue(i);
777             break;
778         }
779
780         if (inObject) {
781             _nextToken = t;
782             return _currToken;
783         }
784         _currToken = t;
785         return t;
786     }
787
788     private final JsonToken _nextAfterName()
789     {
790         _nameCopied = false// need to invalidate if it was copied
791         JsonToken t = _nextToken;
792         _nextToken = null;
793
794 // !!! 16-Nov-2015, tatu: TODO: fix [databind#37], copy next location to current here
795         
796         // Also: may need to start new context?
797         if (t == JsonToken.START_ARRAY) {
798             _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
799         } else if (t == JsonToken.START_OBJECT) {
800             _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
801         }
802         return (_currToken = t);
803     }
804
805     @Override
806     public void finishToken() throws IOException {
807         if (_tokenIncomplete) {
808             _tokenIncomplete = false;
809             _finishString(); // only strings can be incomplete
810         }
811     }
812
813     /*
814     /**********************************************************
815     /* Public API, nextXxx() overrides
816     /**********************************************************
817      */

818
819     // Implemented since 2.7
820     @Override
821     public boolean nextFieldName(SerializableString sstr) throws IOException
822     {
823         // // // Note: most of code below is copied from nextToken()
824
825         _numTypesValid = NR_UNKNOWN;
826         if (_currToken == JsonToken.FIELD_NAME) {
827             _nextAfterName();
828             return false;
829         }
830         if (_tokenIncomplete) {
831             _skipString();
832         }
833         int i = _skipWSOrEnd();
834         if (i < 0) {
835             close();
836             _currToken = null;
837             return false;
838         }
839         _binaryValue = null;
840
841         // Closing scope?
842         if (i == INT_RBRACKET || i == INT_RCURLY) {
843             _closeScope(i);
844             return false;
845         }
846
847         if (_parsingContext.expectComma()) {
848             i = _skipComma(i);
849
850             // Was that a trailing comma?
851             if ((_features & FEAT_MASK_TRAILING_COMMA) != 0) {
852                 if ((i == INT_RBRACKET) || (i == INT_RCURLY)) {
853                     _closeScope(i);
854                     return false;
855                 }
856             }
857         }
858
859         if (!_parsingContext.inObject()) {
860             _updateLocation();
861             _nextTokenNotInObject(i);
862             return false;
863         }
864
865         _updateNameLocation();
866         if (i == INT_QUOTE) {
867             // when doing literal match, must consider escaping:
868             char[] nameChars = sstr.asQuotedChars();
869             final int len = nameChars.length;
870
871             // Require 4 more bytes for faster skipping of colon that follows name
872             if ((_inputPtr + len + 4) < _inputEnd) { // maybe...
873                 // first check length match by
874                 final int end = _inputPtr+len;
875                 if (_inputBuffer[end] == '"') {
876                     int offset = 0;
877                     int ptr = _inputPtr;
878                     while (true) {
879                         if (ptr == end) { // yes, match!
880                             _parsingContext.setCurrentName(sstr.getValue());
881                             _isNextTokenNameYes(_skipColonFast(ptr+1));
882                             return true;
883                         }
884                         if (nameChars[offset] != _inputBuffer[ptr]) {
885                             break;
886                         }
887                         ++offset;
888                         ++ptr;
889                     }
890                 }
891             }
892         }
893         return _isNextTokenNameMaybe(i, sstr.getValue());
894     }
895
896     @Override
897     public String nextFieldName() throws IOException
898     {
899         // // // Note: this is almost a verbatim copy of nextToken() (minus comments)
900
901         _numTypesValid = NR_UNKNOWN;
902         if (_currToken == JsonToken.FIELD_NAME) {
903             _nextAfterName();
904             return null;
905         }
906         if (_tokenIncomplete) {
907             _skipString();
908         }
909         int i = _skipWSOrEnd();
910         if (i < 0) {
911             close();
912             _currToken = null;
913             return null;
914         }
915         _binaryValue = null;
916         if (i == INT_RBRACKET || i == INT_RCURLY) {
917             _closeScope(i);
918             return null;
919         }
920         if (_parsingContext.expectComma()) {
921             i = _skipComma(i);
922             if ((_features & FEAT_MASK_TRAILING_COMMA) != 0) {
923                 if ((i == INT_RBRACKET) || (i == INT_RCURLY)) {
924                     _closeScope(i);
925                     return null;
926                 }
927             }
928         }
929         if (!_parsingContext.inObject()) {
930             _updateLocation();
931             _nextTokenNotInObject(i);
932             return null;
933         }
934
935         _updateNameLocation();
936         String name = (i == INT_QUOTE) ? _parseName() : _handleOddName(i);
937         _parsingContext.setCurrentName(name);
938         _currToken = JsonToken.FIELD_NAME;
939         i = _skipColon();
940
941         _updateLocation();
942         if (i == INT_QUOTE) {
943             _tokenIncomplete = true;
944             _nextToken = JsonToken.VALUE_STRING;
945             return name;
946         }
947         
948         // Ok: we must have a value... what is it?
949
950         JsonToken t;
951
952         switch (i) {
953         case '-':
954             t = _parseNegNumber();
955             break;
956         case '.': // [core#61]]
957             t = _parseFloatThatStartsWithPeriod();
958             break;
959         case '0':
960         case '1':
961         case '2':
962         case '3':
963         case '4':
964         case '5':
965         case '6':
966         case '7':
967         case '8':
968         case '9':
969             t = _parsePosNumber(i);
970             break;
971         case 'f':
972             _matchFalse();
973             t = JsonToken.VALUE_FALSE;
974             break;
975         case 'n':
976             _matchNull();
977             t = JsonToken.VALUE_NULL;
978             break;
979         case 't':
980             _matchTrue();
981             t = JsonToken.VALUE_TRUE;
982             break;
983         case '[':
984             t = JsonToken.START_ARRAY;
985             break;
986         case '{':
987             t = JsonToken.START_OBJECT;
988             break;
989         default:
990             t = _handleOddValue(i);
991             break;
992         }
993         _nextToken = t;
994         return name;
995     }
996
997     private final void _isNextTokenNameYes(int i) throws IOException
998     {
999         _currToken = JsonToken.FIELD_NAME;
1000         _updateLocation();
1001
1002         switch (i) {
1003         case '"':
1004             _tokenIncomplete = true;
1005             _nextToken = JsonToken.VALUE_STRING;
1006             return;
1007         case '[':
1008             _nextToken = JsonToken.START_ARRAY;
1009             return;
1010         case '{':
1011             _nextToken = JsonToken.START_OBJECT;
1012             return;
1013         case 't':
1014             _matchToken("true", 1);
1015             _nextToken = JsonToken.VALUE_TRUE;
1016             return;
1017         case 'f':
1018             _matchToken("false", 1);
1019             _nextToken = JsonToken.VALUE_FALSE;
1020             return;
1021         case 'n':
1022             _matchToken("null", 1);
1023             _nextToken = JsonToken.VALUE_NULL;
1024             return;
1025         case '-':
1026             _nextToken = _parseNegNumber();
1027             return;
1028         case '.': // [core#61]]
1029             _nextToken = _parseFloatThatStartsWithPeriod();
1030             return;
1031         case '0':
1032         case '1':
1033         case '2':
1034         case '3':
1035         case '4':
1036         case '5':
1037         case '6':
1038         case '7':
1039         case '8':
1040         case '9':
1041             _nextToken = _parsePosNumber(i);
1042             return;
1043         }
1044         _nextToken = _handleOddValue(i);
1045     }
1046
1047     protected boolean _isNextTokenNameMaybe(int i, String nameToMatch) throws IOException
1048     {
1049         // // // and this is back to standard nextToken()
1050         String name = (i == INT_QUOTE) ? _parseName() : _handleOddName(i);
1051         _parsingContext.setCurrentName(name);
1052         _currToken = JsonToken.FIELD_NAME;
1053         i = _skipColon();
1054         _updateLocation();
1055         if (i == INT_QUOTE) {
1056             _tokenIncomplete = true;
1057             _nextToken = JsonToken.VALUE_STRING;
1058             return nameToMatch.equals(name);
1059         }
1060         // Ok: we must have a value... what is it?
1061         JsonToken t;
1062         switch (i) {
1063         case '-':
1064             t = _parseNegNumber();
1065             break;
1066         case '.': // [core#61]]
1067             t = _parseFloatThatStartsWithPeriod();
1068             break;
1069         case '0':
1070         case '1':
1071         case '2':
1072         case '3':
1073         case '4':
1074         case '5':
1075         case '6':
1076         case '7':
1077         case '8':
1078         case '9':
1079             t = _parsePosNumber(i);
1080             break;
1081         case 'f':
1082             _matchFalse();
1083             t = JsonToken.VALUE_FALSE;
1084             break;
1085         case 'n':
1086             _matchNull();
1087             t = JsonToken.VALUE_NULL;
1088             break;
1089         case 't':
1090             _matchTrue();
1091             t = JsonToken.VALUE_TRUE;
1092             break;
1093         case '[':
1094             t = JsonToken.START_ARRAY;
1095             break;
1096         case '{':
1097             t = JsonToken.START_OBJECT;
1098             break;
1099         default:
1100             t = _handleOddValue(i);
1101             break;
1102         }
1103         _nextToken = t;
1104         return nameToMatch.equals(name);
1105     }
1106
1107     private final JsonToken _nextTokenNotInObject(int i) throws IOException
1108     {
1109         if (i == INT_QUOTE) {
1110             _tokenIncomplete = true;
1111             return (_currToken = JsonToken.VALUE_STRING);
1112         }
1113         switch (i) {
1114         case '[':
1115             _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1116             return (_currToken = JsonToken.START_ARRAY);
1117         case '{':
1118             _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1119             return (_currToken = JsonToken.START_OBJECT);
1120         case 't':
1121             _matchToken("true", 1);
1122             return (_currToken = JsonToken.VALUE_TRUE);
1123         case 'f':
1124             _matchToken("false", 1);
1125             return (_currToken = JsonToken.VALUE_FALSE);
1126         case 'n':
1127             _matchToken("null", 1);
1128             return (_currToken = JsonToken.VALUE_NULL);
1129         case '-':
1130             return (_currToken = _parseNegNumber());
1131             /* Should we have separate handling for plus? Although
1132              * it is not allowed per se, it may be erroneously used,
1133              * and could be indicated by a more specific error message.
1134              */

1135         case '.': // [core#61]]
1136             return (_currToken = _parseFloatThatStartsWithPeriod());
1137         case '0':
1138         case '1':
1139         case '2':
1140         case '3':
1141         case '4':
1142         case '5':
1143         case '6':
1144         case '7':
1145         case '8':
1146         case '9':
1147             return (_currToken = _parsePosNumber(i));
1148         /*
1149          * This check proceeds only if the Feature.ALLOW_MISSING_VALUES is enabled
1150          * The Check is for missing values. In case of missing values in an array, the next token will be either ',' or ']'.
1151          * This case, decrements the already incremented _inputPtr in the buffer in case of comma(,) 
1152          * so that the existing flow goes back to checking the next token which will be comma again and
1153          * it continues the parsing.
1154          * Also the case returns NULL as current token in case of ',' or ']'.    
1155          */

1156 // case ']':  // 11-May-2020, tatu: related to [core#616], this should never be reached
1157         case ',':
1158             // 11-May-2020, tatu: [core#616] No commas in root level
1159             if (!_parsingContext.inRoot()) {
1160                 if ((_features & FEAT_MASK_ALLOW_MISSING) != 0) {
1161                     --_inputPtr;
1162                     return (_currToken = JsonToken.VALUE_NULL);
1163                 }
1164             }
1165         }
1166         return (_currToken = _handleOddValue(i));
1167     }
1168     // note: identical to one in UTF8StreamJsonParser
1169     @Override
1170     public final String nextTextValue() throws IOException
1171     {
1172         if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
1173             _nameCopied = false;
1174             JsonToken t = _nextToken;
1175             _nextToken = null;
1176             _currToken = t;
1177             if (t == JsonToken.VALUE_STRING) {
1178                 if (_tokenIncomplete) {
1179                     _tokenIncomplete = false;
1180                     _finishString();
1181                 }
1182                 return _textBuffer.contentsAsString();
1183             }
1184             if (t == JsonToken.START_ARRAY) {
1185                 _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1186             } else if (t == JsonToken.START_OBJECT) {
1187                 _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1188             }
1189             return null;
1190         }
1191         // !!! TODO: optimize this case as well
1192         return (nextToken() == JsonToken.VALUE_STRING) ? getText() : null;
1193     }
1194
1195     // note: identical to one in Utf8StreamParser
1196     @Override
1197     public final int nextIntValue(int defaultValue) throws IOException
1198     {
1199         if (_currToken == JsonToken.FIELD_NAME) {
1200             _nameCopied = false;
1201             JsonToken t = _nextToken;
1202             _nextToken = null;
1203             _currToken = t;
1204             if (t == JsonToken.VALUE_NUMBER_INT) {
1205                 return getIntValue();
1206             }
1207             if (t == JsonToken.START_ARRAY) {
1208                 _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1209             } else if (t == JsonToken.START_OBJECT) {
1210                 _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1211             }
1212             return defaultValue;
1213         }
1214         // !!! TODO: optimize this case as well
1215         return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getIntValue() : defaultValue;
1216     }
1217
1218     // note: identical to one in Utf8StreamParser
1219     @Override
1220     public final long nextLongValue(long defaultValue) throws IOException
1221     {
1222         if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
1223             _nameCopied = false;
1224             JsonToken t = _nextToken;
1225             _nextToken = null;
1226             _currToken = t;
1227             if (t == JsonToken.VALUE_NUMBER_INT) {
1228                 return getLongValue();
1229             }
1230             if (t == JsonToken.START_ARRAY) {
1231                 _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1232             } else if (t == JsonToken.START_OBJECT) {
1233                 _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1234             }
1235             return defaultValue;
1236         }
1237         // !!! TODO: optimize this case as well
1238         return (nextToken() == JsonToken.VALUE_NUMBER_INT) ? getLongValue() : defaultValue;
1239     }
1240
1241     // note: identical to one in UTF8StreamJsonParser
1242     @Override
1243     public final Boolean nextBooleanValue() throws IOException
1244     {
1245         if (_currToken == JsonToken.FIELD_NAME) { // mostly copied from '_nextAfterName'
1246             _nameCopied = false;
1247             JsonToken t = _nextToken;
1248             _nextToken = null;
1249             _currToken = t;
1250             if (t == JsonToken.VALUE_TRUE) {
1251                 return Boolean.TRUE;
1252             }
1253             if (t == JsonToken.VALUE_FALSE) {
1254                 return Boolean.FALSE;
1255             }
1256             if (t == JsonToken.START_ARRAY) {
1257                 _parsingContext = _parsingContext.createChildArrayContext(_tokenInputRow, _tokenInputCol);
1258             } else if (t == JsonToken.START_OBJECT) {
1259                 _parsingContext = _parsingContext.createChildObjectContext(_tokenInputRow, _tokenInputCol);
1260             }
1261             return null;
1262         }
1263         JsonToken t = nextToken();
1264         if (t != null) {
1265             int id = t.id();
1266             if (id == ID_TRUE) return Boolean.TRUE;
1267             if (id == ID_FALSE) return Boolean.FALSE;
1268         }
1269         return null;
1270     }
1271
1272     /*
1273     /**********************************************************
1274     /* Internal methods, number parsing
1275     /**********************************************************
1276      */

1277
1278     // @since 2.11, [core#611]
1279     protected final JsonToken _parseFloatThatStartsWithPeriod() throws IOException
1280     {
1281         // [core#611]: allow optionally leading decimal point
1282         if (!isEnabled(JsonReadFeature.ALLOW_LEADING_DECIMAL_POINT_FOR_NUMBERS.mappedFeature())) {
1283             return _handleOddValue('.');
1284         }
1285         return _parseFloat(INT_PERIOD, _inputPtr-1, _inputPtr, false, 0);
1286     }
1287
1288     /**
1289      * Initial parsing method for number values. It needs to be able
1290      * to parse enough input to be able to determine whether the
1291      * value is to be considered a simple integer value, or a more
1292      * generic decimal value: latter of which needs to be expressed
1293      * as a floating point number. The basic rule is that if the number
1294      * has no fractional or exponential part, it is an integer; otherwise
1295      * a floating point number.
1296      *<p>
1297      * Because much of input has to be processed in any case, no partial
1298      * parsing is done: all input text will be stored for further
1299      * processing. However, actual numeric value conversion will be
1300      * deferred, since it is usually the most complicated and costliest
1301      * part of processing.
1302      */

1303     protected final JsonToken _parsePosNumber(int ch) throws IOException
1304     {
1305         /* Although we will always be complete with respect to textual
1306          * representation (that is, all characters will be parsed),
1307          * actual conversion to a number is deferred. Thus, need to
1308          * note that no representations are valid yet
1309          */

1310         int ptr = _inputPtr;
1311         int startPtr = ptr-1; // to include digit already read
1312         final int inputLen = _inputEnd;
1313
1314         // One special case, leading zero(es):
1315         if (ch == INT_0) {
1316             return _parseNumber2(false, startPtr);
1317         }
1318
1319         /* First, let's see if the whole number is contained within
1320          * the input buffer unsplit. This should be the common case;
1321          * and to simplify processing, we will just reparse contents
1322          * in the alternative case (number split on buffer boundary)
1323          */

1324
1325         int intLen = 1; // already got one
1326
1327         // First let's get the obligatory integer part:
1328         int_loop:
1329         while (true) {
1330             if (ptr >= inputLen) {
1331                 _inputPtr = startPtr;
1332                 return _parseNumber2(false, startPtr);
1333             }
1334             ch = (int) _inputBuffer[ptr++];
1335             if (ch < INT_0 || ch > INT_9) {
1336                 break int_loop;
1337             }
1338             ++intLen;
1339         }
1340         if (ch == INT_PERIOD || ch == INT_e || ch == INT_E) {
1341             _inputPtr = ptr;
1342             return _parseFloat(ch, startPtr, ptr, false, intLen);
1343         }
1344         // Got it all: let's add to text buffer for parsing, access
1345         --ptr; // need to push back following separator
1346         _inputPtr = ptr;
1347         // As per #105, need separating space between root values; check here
1348         if (_parsingContext.inRoot()) {
1349             _verifyRootSpace(ch);
1350         }
1351         int len = ptr-startPtr;
1352         _textBuffer.resetWithShared(_inputBuffer, startPtr, len);
1353         return resetInt(false, intLen);
1354     }
1355
1356     private final JsonToken _parseFloat(int ch, int startPtr, int ptr, boolean neg, int intLen)
1357         throws IOException
1358     {
1359         final int inputLen = _inputEnd;
1360         int fractLen = 0;
1361
1362         // And then see if we get other parts
1363         if (ch == '.') { // yes, fraction
1364             fract_loop:
1365             while (true) {
1366                 if (ptr >= inputLen) {
1367                     return _parseNumber2(neg, startPtr);
1368                 }
1369                 ch = (int) _inputBuffer[ptr++];
1370                 if (ch < INT_0 || ch > INT_9) {
1371                     break fract_loop;
1372                 }
1373                 ++fractLen;
1374             }
1375             // must be followed by sequence of ints, one minimum
1376             if (fractLen == 0) {
1377                 reportUnexpectedNumberChar(ch, "Decimal point not followed by a digit");
1378             }
1379         }
1380         int expLen = 0;
1381         if (ch == 'e' || ch == 'E') { // and/or exponent
1382             if (ptr >= inputLen) {
1383                 _inputPtr = startPtr;
1384                 return _parseNumber2(neg, startPtr);
1385             }
1386             // Sign indicator?
1387             ch = (int) _inputBuffer[ptr++];
1388             if (ch == INT_MINUS || ch == INT_PLUS) { // yup, skip for now
1389                 if (ptr >= inputLen) {
1390                     _inputPtr = startPtr;
1391                     return _parseNumber2(neg, startPtr);
1392                 }
1393                 ch = (int) _inputBuffer[ptr++];
1394             }
1395             while (ch <= INT_9 && ch >= INT_0) {
1396                 ++expLen;
1397                 if (ptr >= inputLen) {
1398                     _inputPtr = startPtr;
1399                     return _parseNumber2(neg, startPtr);
1400                 }
1401                 ch = (int) _inputBuffer[ptr++];
1402             }
1403             // must be followed by sequence of ints, one minimum
1404             if (expLen == 0) {
1405                 reportUnexpectedNumberChar(ch, "Exponent indicator not followed by a digit");
1406             }
1407         }
1408         --ptr; // need to push back following separator
1409         _inputPtr = ptr;
1410         // As per #105, need separating space between root values; check here
1411         if (_parsingContext.inRoot()) {
1412             _verifyRootSpace(ch);
1413         }
1414         int len = ptr-startPtr;
1415         _textBuffer.resetWithShared(_inputBuffer, startPtr, len);
1416         // And there we have it!
1417         return resetFloat(neg, intLen, fractLen, expLen);
1418     }
1419
1420     protected final JsonToken _parseNegNumber() throws IOException
1421     {
1422         int ptr = _inputPtr;
1423         int startPtr = ptr-1; // to include sign/digit already read
1424         final int inputLen = _inputEnd;
1425
1426         if (ptr >= inputLen) {
1427             return _parseNumber2(true, startPtr);
1428         }
1429         int ch = _inputBuffer[ptr++];
1430         // First check: must have a digit to follow minus sign
1431         if (ch > INT_9 || ch < INT_0) {
1432             _inputPtr = ptr;
1433             return _handleInvalidNumberStart(ch, true);
1434         }
1435         // One special case, leading zero(es):
1436         if (ch == INT_0) {
1437             return _parseNumber2(true, startPtr);
1438         }
1439         int intLen = 1; // already got one
1440
1441         // First let's get the obligatory integer part:
1442         int_loop:
1443         while (true) {
1444             if (ptr >= inputLen) {
1445                 return _parseNumber2(true, startPtr);
1446             }
1447             ch = (int) _inputBuffer[ptr++];
1448             if (ch < INT_0 || ch > INT_9) {
1449                 break int_loop;
1450             }
1451             ++intLen;
1452         }
1453
1454         if (ch == INT_PERIOD || ch == INT_e || ch == INT_E) {
1455             _inputPtr = ptr;
1456             return _parseFloat(ch, startPtr, ptr, true, intLen);
1457         }
1458         --ptr;
1459         _inputPtr = ptr;
1460         if (_parsingContext.inRoot()) {
1461             _verifyRootSpace(ch);
1462         }
1463         int len = ptr-startPtr;
1464         _textBuffer.resetWithShared(_inputBuffer, startPtr, len);
1465         return resetInt(true, intLen);
1466     }
1467
1468     /**
1469      * Method called to parse a number, when the primary parse
1470      * method has failed to parse it, due to it being split on
1471      * buffer boundary. As a result code is very similar, except
1472      * that it has to explicitly copy contents to the text buffer
1473      * instead of just sharing the main input buffer.
1474      */

1475     private final JsonToken _parseNumber2(boolean neg, int startPtr) throws IOException
1476     {
1477         _inputPtr = neg ? (startPtr+1) : startPtr;
1478         char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
1479         int outPtr = 0;
1480
1481         // Need to prepend sign?
1482         if (neg) {
1483             outBuf[outPtr++] = '-';
1484         }
1485
1486         // This is the place to do leading-zero check(s) too:
1487         int intLen = 0;
1488         char c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++]
1489                 : getNextChar("No digit following minus sign", JsonToken.VALUE_NUMBER_INT);
1490         if (c == '0') {
1491             c = _verifyNoLeadingZeroes();
1492         }
1493         boolean eof = false;
1494
1495         // Ok, first the obligatory integer part:
1496         int_loop:
1497         while (c >= '0' && c <= '9') {
1498             ++intLen;
1499             if (outPtr >= outBuf.length) {
1500                 outBuf = _textBuffer.finishCurrentSegment();
1501                 outPtr = 0;
1502             }
1503             outBuf[outPtr++] = c;
1504             if (_inputPtr >= _inputEnd && !_loadMore()) {
1505                 // EOF is legal for main level int values
1506                 c = CHAR_NULL;
1507                 eof = true;
1508                 break int_loop;
1509             }
1510             c = _inputBuffer[_inputPtr++];
1511         }
1512         // Also, integer part is not optional
1513         if (intLen == 0) {
1514             return _handleInvalidNumberStart(c, neg);
1515         }
1516
1517         int fractLen = 0;
1518         // And then see if we get other parts
1519         if (c == '.') { // yes, fraction
1520             if (outPtr >= outBuf.length) {
1521                 outBuf = _textBuffer.finishCurrentSegment();
1522                 outPtr = 0;
1523             }
1524             outBuf[outPtr++] = c;
1525
1526             fract_loop:
1527             while (true) {
1528                 if (_inputPtr >= _inputEnd && !_loadMore()) {
1529                     eof = true;
1530                     break fract_loop;
1531                 }
1532                 c = _inputBuffer[_inputPtr++];
1533                 if (c < INT_0 || c > INT_9) {
1534                     break fract_loop;
1535                 }
1536                 ++fractLen;
1537                 if (outPtr >= outBuf.length) {
1538                     outBuf = _textBuffer.finishCurrentSegment();
1539                     outPtr = 0;
1540                 }
1541                 outBuf[outPtr++] = c;
1542             }
1543             // must be followed by sequence of ints, one minimum
1544             if (fractLen == 0) {
1545                 reportUnexpectedNumberChar(c, "Decimal point not followed by a digit");
1546             }
1547         }
1548
1549         int expLen = 0;
1550         if (c == 'e' || c == 'E') { // exponent?
1551             if (outPtr >= outBuf.length) {
1552                 outBuf = _textBuffer.finishCurrentSegment();
1553                 outPtr = 0;
1554             }
1555             outBuf[outPtr++] = c;
1556             // Not optional, can require that we get one more char
1557             c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++]
1558                 : getNextChar("expected a digit for number exponent");
1559             // Sign indicator?
1560             if (c == '-' || c == '+') {
1561                 if (outPtr >= outBuf.length) {
1562                     outBuf = _textBuffer.finishCurrentSegment();
1563                     outPtr = 0;
1564                 }
1565                 outBuf[outPtr++] = c;
1566                 // Likewise, non optional:
1567                 c = (_inputPtr < _inputEnd) ? _inputBuffer[_inputPtr++]
1568                     : getNextChar("expected a digit for number exponent");
1569             }
1570
1571             exp_loop:
1572             while (c <= INT_9 && c >= INT_0) {
1573                 ++expLen;
1574                 if (outPtr >= outBuf.length) {
1575                     outBuf = _textBuffer.finishCurrentSegment();
1576                     outPtr = 0;
1577                 }
1578                 outBuf[outPtr++] = c;
1579                 if (_inputPtr >= _inputEnd && !_loadMore()) {
1580                     eof = true;
1581                     break exp_loop;
1582                 }
1583                 c = _inputBuffer[_inputPtr++];
1584             }
1585             // must be followed by sequence of ints, one minimum
1586             if (expLen == 0) {
1587                 reportUnexpectedNumberChar(c, "Exponent indicator not followed by a digit");
1588             }
1589         }
1590
1591         // Ok; unless we hit end-of-input, need to push last char read back
1592         if (!eof) {
1593             --_inputPtr;
1594             if (_parsingContext.inRoot()) {
1595                 _verifyRootSpace(c);
1596             }
1597         }
1598         _textBuffer.setCurrentLength(outPtr);
1599         // And there we have it!
1600         return reset(neg, intLen, fractLen, expLen);
1601     }
1602
1603     /**
1604      * Method called when we have seen one zero, and want to ensure
1605      * it is not followed by another
1606      */

1607     private final char _verifyNoLeadingZeroes() throws IOException
1608     {
1609         // Fast case first:
1610         if (_inputPtr < _inputEnd) {
1611             char ch = _inputBuffer[_inputPtr];
1612             // if not followed by a number (probably '.'); return zero as is, to be included
1613             if (ch < '0' || ch > '9') {
1614                 return '0';
1615             }
1616         }
1617         // and offline the less common case
1618         return _verifyNLZ2();
1619     }
1620
1621     private char _verifyNLZ2() throws IOException
1622     {
1623         if (_inputPtr >= _inputEnd && !_loadMore()) {
1624             return '0';
1625         }
1626         char ch = _inputBuffer[_inputPtr];
1627         if (ch < '0' || ch > '9') {
1628             return '0';
1629         }
1630         if ((_features & FEAT_MASK_LEADING_ZEROS) == 0) {
1631             reportInvalidNumber("Leading zeroes not allowed");
1632         }
1633         // if so, just need to skip either all zeroes (if followed by number); or all but one (if non-number)
1634         ++_inputPtr; // Leading zero to be skipped
1635         if (ch == INT_0) {
1636             while (_inputPtr < _inputEnd || _loadMore()) {
1637                 ch = _inputBuffer[_inputPtr];
1638                 if (ch < '0' || ch > '9') { // followed by non-number; retain one zero
1639                     return '0';
1640                 }
1641                 ++_inputPtr; // skip previous zero
1642                 if (ch != '0') { // followed by other number; return
1643                     break;
1644                 }
1645             }
1646         }
1647         return ch;
1648     }
1649
1650     /**
1651      * Method called if expected numeric value (due to leading sign) does not
1652      * look like a number
1653      */

1654     protected JsonToken _handleInvalidNumberStart(int ch, boolean negative) throws IOException
1655     {
1656         if (ch == 'I') {
1657             if (_inputPtr >= _inputEnd) {
1658                 if (!_loadMore()) {
1659                     _reportInvalidEOFInValue(JsonToken.VALUE_NUMBER_INT);
1660                 }
1661             }
1662             ch = _inputBuffer[_inputPtr++];
1663             if (ch == 'N') {
1664                 String match = negative ? "-INF" :"+INF";
1665                 _matchToken(match, 3);
1666                 if ((_features & FEAT_MASK_NON_NUM_NUMBERS) != 0) {
1667                     return resetAsNaN(match, negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
1668                 }
1669                 _reportError("Non-standard token '"+match+"': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
1670             } else if (ch == 'n') {
1671                 String match = negative ? "-Infinity" :"+Infinity";
1672                 _matchToken(match, 3);
1673                 if ((_features & FEAT_MASK_NON_NUM_NUMBERS) != 0) {
1674                     return resetAsNaN(match, negative ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
1675                 }
1676                 _reportError("Non-standard token '"+match+"': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
1677             }
1678         }
1679         reportUnexpectedNumberChar(ch, "expected digit (0-9) to follow minus sign, for valid numeric value");
1680         return null;
1681     }
1682
1683     /**
1684      * Method called to ensure that a root-value is followed by a space
1685      * token.
1686      *<p>
1687      * NOTE: caller MUST ensure there is at least one character available;
1688      * and that input pointer is AT given char (not past)
1689      */

1690     private final void _verifyRootSpace(int ch) throws IOException
1691     {
1692         // caller had pushed it back, before calling; reset
1693         ++_inputPtr;
1694         switch (ch) {
1695         case ' ':
1696         case '\t':
1697             return;
1698         case '\r':
1699             _skipCR();
1700             return;
1701         case '\n':
1702             ++_currInputRow;
1703             _currInputRowStart = _inputPtr;
1704             return;
1705         }
1706         _reportMissingRootWS(ch);
1707     }
1708
1709     /*
1710     /**********************************************************
1711     /* Internal methods, secondary parsing
1712     /**********************************************************
1713      */

1714
1715     protected final String _parseName() throws IOException
1716     {
1717         // First: let's try to see if we have a simple name: one that does
1718         // not cross input buffer boundary, and does not contain escape sequences.
1719         int ptr = _inputPtr;
1720         int hash = _hashSeed;
1721         final int[] codes = _icLatin1;
1722
1723         while (ptr < _inputEnd) {
1724             int ch = _inputBuffer[ptr];
1725             if (ch < codes.length && codes[ch] != 0) {
1726                 if (ch == '"') {
1727                     int start = _inputPtr;
1728                     _inputPtr = ptr+1; // to skip the quote
1729                     return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
1730                 }
1731                 break;
1732             }
1733             hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
1734             ++ptr;
1735         }
1736         int start = _inputPtr;
1737         _inputPtr = ptr;
1738         return _parseName2(start, hash, INT_QUOTE);
1739     }
1740
1741     private String _parseName2(int startPtr, int hash, int endChar) throws IOException
1742     {
1743         _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr));
1744
1745         /* Output pointers; calls will also ensure that the buffer is
1746          * not shared and has room for at least one more char.
1747          */

1748         char[] outBuf = _textBuffer.getCurrentSegment();
1749         int outPtr = _textBuffer.getCurrentSegmentSize();
1750
1751         while (true) {
1752             if (_inputPtr >= _inputEnd) {
1753                 if (!_loadMore()) {
1754                     _reportInvalidEOF(" in field name", JsonToken.FIELD_NAME);
1755                 }
1756             }
1757             char c = _inputBuffer[_inputPtr++];
1758             int i = (int) c;
1759             if (i <= INT_BACKSLASH) {
1760                 if (i == INT_BACKSLASH) {
1761                     /* Although chars outside of BMP are to be escaped as
1762                      * an UTF-16 surrogate pair, does that affect decoding?
1763                      * For now let's assume it does not.
1764                      */

1765                     c = _decodeEscaped();
1766                 } else if (i <= endChar) {
1767                     if (i == endChar) {
1768                         break;
1769                     }
1770                     if (i < INT_SPACE) {
1771                         _throwUnquotedSpace(i, "name");
1772                     }
1773                 }
1774             }
1775             hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + c;
1776             // Ok, let's add char to output:
1777             outBuf[outPtr++] = c;
1778
1779             // Need more room?
1780             if (outPtr >= outBuf.length) {
1781                 outBuf = _textBuffer.finishCurrentSegment();
1782                 outPtr = 0;
1783             }
1784         }
1785         _textBuffer.setCurrentLength(outPtr);
1786         {
1787             TextBuffer tb = _textBuffer;
1788             char[] buf = tb.getTextBuffer();
1789             int start = tb.getTextOffset();
1790             int len = tb.size();
1791             return _symbols.findSymbol(buf, start, len, hash);
1792         }
1793     }
1794
1795     /**
1796      * Method called when we see non-white space character other
1797      * than double quote, when expecting a field name.
1798      * In standard mode will just throw an expection; but
1799      * in non-standard modes may be able to parse name.
1800      */

1801     protected String _handleOddName(int i) throws IOException
1802     {
1803         // [JACKSON-173]: allow single quotes
1804         if (i == '\'' && (_features & FEAT_MASK_ALLOW_SINGLE_QUOTES) != 0) {
1805             return _parseAposName();
1806         }
1807         // [JACKSON-69]: allow unquoted names if feature enabled:
1808         if ((_features & FEAT_MASK_ALLOW_UNQUOTED_NAMES) == 0) {
1809             _reportUnexpectedChar(i, "was expecting double-quote to start field name");
1810         }
1811         final int[] codes = CharTypes.getInputCodeLatin1JsNames();
1812         final int maxCode = codes.length;
1813
1814         // Also: first char must be a valid name char, but NOT be number
1815         boolean firstOk;
1816
1817         if (i < maxCode) { // identifier, or a number ([Issue#102])
1818             firstOk = (codes[i] == 0);
1819         } else {
1820             firstOk = Character.isJavaIdentifierPart((char) i);
1821         }
1822         if (!firstOk) {
1823             _reportUnexpectedChar(i, "was expecting either valid name character (for unquoted name) or double-quote (for quoted) to start field name");
1824         }
1825         int ptr = _inputPtr;
1826         int hash = _hashSeed;
1827         final int inputLen = _inputEnd;
1828
1829         if (ptr < inputLen) {
1830             do {
1831                 int ch = _inputBuffer[ptr];
1832                 if (ch < maxCode) {
1833                     if (codes[ch] != 0) {
1834                         int start = _inputPtr-1; // -1 to bring back first char
1835                         _inputPtr = ptr;
1836                         return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
1837                     }
1838                 } else if (!Character.isJavaIdentifierPart((char) ch)) {
1839                     int start = _inputPtr-1; // -1 to bring back first char
1840                     _inputPtr = ptr;
1841                     return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
1842                 }
1843                 hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
1844                 ++ptr;
1845             } while (ptr < inputLen);
1846         }
1847         int start = _inputPtr-1;
1848         _inputPtr = ptr;
1849         return _handleOddName2(start, hash, codes);
1850     }
1851
1852     protected String _parseAposName() throws IOException
1853     {
1854         // Note: mostly copy of_parseFieldName
1855         int ptr = _inputPtr;
1856         int hash = _hashSeed;
1857         final int inputLen = _inputEnd;
1858
1859         if (ptr < inputLen) {
1860             final int[] codes = _icLatin1;
1861             final int maxCode = codes.length;
1862
1863             do {
1864                 int ch = _inputBuffer[ptr];
1865                 if (ch == '\'') {
1866                     int start = _inputPtr;
1867                     _inputPtr = ptr+1; // to skip the quote
1868                     return _symbols.findSymbol(_inputBuffer, start, ptr - start, hash);
1869                 }
1870                 if (ch < maxCode && codes[ch] != 0) {
1871                     break;
1872                 }
1873                 hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + ch;
1874                 ++ptr;
1875             } while (ptr < inputLen);
1876         }
1877
1878         int start = _inputPtr;
1879         _inputPtr = ptr;
1880
1881         return _parseName2(start, hash, '\'');
1882     }
1883
1884     /**
1885      * Method for handling cases where first non-space character
1886      * of an expected value token is not legal for standard JSON content.
1887      */

1888     protected JsonToken _handleOddValue(int i) throws IOException
1889     {
1890         // Most likely an error, unless we are to allow single-quote-strings
1891         switch (i) {
1892         case '\'':
1893             /* Allow single quotes? Unlike with regular Strings, we'll eagerly parse
1894              * contents; this so that there'sno need to store information on quote char used.
1895              * Also, no separation to fast/slow parsing; we'll just do
1896              * one regular (~= slowish) parsing, to keep code simple
1897              */

1898             if ((_features & FEAT_MASK_ALLOW_SINGLE_QUOTES) != 0) {
1899                 return _handleApos();
1900             }
1901             break;
1902         case ']':
1903             /* 28-Mar-2016: [core#116]: If Feature.ALLOW_MISSING_VALUES is enabled
1904              *   we may allow "missing values", that is, encountering a trailing
1905              *   comma or closing marker where value would be expected
1906              */

1907             if (!_parsingContext.inArray()) {
1908                 break;
1909             }
1910             // fall through
1911         case ',':
1912             // 11-May-2020, tatu: [core#616] No commas in root level
1913             if (!_parsingContext.inRoot()) {
1914                 if ((_features & FEAT_MASK_ALLOW_MISSING) != 0) {
1915                     --_inputPtr;
1916                     return JsonToken.VALUE_NULL;
1917                 }
1918             }
1919             break;
1920         case 'N':
1921             _matchToken("NaN", 1);
1922             if ((_features & FEAT_MASK_NON_NUM_NUMBERS) != 0) {
1923                 return resetAsNaN("NaN", Double.NaN);
1924             }
1925             _reportError("Non-standard token 'NaN': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
1926             break;
1927         case 'I':
1928             _matchToken("Infinity", 1);
1929             if ((_features & FEAT_MASK_NON_NUM_NUMBERS) != 0) {
1930                 return resetAsNaN("Infinity", Double.POSITIVE_INFINITY);
1931             }
1932             _reportError("Non-standard token 'Infinity': enable JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS to allow");
1933             break;
1934         case '+': // note: '-' is taken as number
1935             if (_inputPtr >= _inputEnd) {
1936                 if (!_loadMore()) {
1937                     _reportInvalidEOFInValue(JsonToken.VALUE_NUMBER_INT);
1938                 }
1939             }
1940             return _handleInvalidNumberStart(_inputBuffer[_inputPtr++], false);
1941         }
1942         // [core#77] Try to decode most likely token
1943         if (Character.isJavaIdentifierStart(i)) {
1944             _reportInvalidToken(""+((char) i), _validJsonTokenList());
1945         }
1946         // but if it doesn't look like a token:
1947         _reportUnexpectedChar(i, "expected a valid value "+_validJsonValueList());
1948         return null;
1949     }
1950
1951     protected JsonToken _handleApos() throws IOException
1952     {
1953         char[] outBuf = _textBuffer.emptyAndGetCurrentSegment();
1954         int outPtr = _textBuffer.getCurrentSegmentSize();
1955
1956         while (true) {
1957             if (_inputPtr >= _inputEnd) {
1958                 if (!_loadMore()) {
1959                     _reportInvalidEOF(": was expecting closing quote for a string value",
1960                             JsonToken.VALUE_STRING);
1961                 }
1962             }
1963             char c = _inputBuffer[_inputPtr++];
1964             int i = (int) c;
1965             if (i <= '\\') {
1966                 if (i == '\\') {
1967                     /* Although chars outside of BMP are to be escaped as
1968                      * an UTF-16 surrogate pair, does that affect decoding?
1969                      * For now let's assume it does not.
1970                      */

1971                     c = _decodeEscaped();
1972                 } else if (i <= '\'') {
1973                     if (i == '\'') {
1974                         break;
1975                     }
1976                     if (i < INT_SPACE) {
1977                         _throwUnquotedSpace(i, "string value");
1978                     }
1979                 }
1980             }
1981             // Need more room?
1982             if (outPtr >= outBuf.length) {
1983                 outBuf = _textBuffer.finishCurrentSegment();
1984                 outPtr = 0;
1985             }
1986             // Ok, let's add char to output:
1987             outBuf[outPtr++] = c;
1988         }
1989         _textBuffer.setCurrentLength(outPtr);
1990         return JsonToken.VALUE_STRING;
1991     }
1992
1993     private String _handleOddName2(int startPtr, int hash, int[] codes) throws IOException
1994     {
1995         _textBuffer.resetWithShared(_inputBuffer, startPtr, (_inputPtr - startPtr));
1996         char[] outBuf = _textBuffer.getCurrentSegment();
1997         int outPtr = _textBuffer.getCurrentSegmentSize();
1998         final int maxCode = codes.length;
1999
2000         while (true) {
2001             if (_inputPtr >= _inputEnd) {
2002                 if (!_loadMore()) { // acceptable for now (will error out later)
2003                     break;
2004                 }
2005             }
2006             char c = _inputBuffer[_inputPtr];
2007             int i = (int) c;
2008             if (i < maxCode) {
2009                 if (codes[i] != 0) {
2010                     break;
2011                 }
2012             } else if (!Character.isJavaIdentifierPart(c)) {
2013                 break;
2014             }
2015             ++_inputPtr;
2016             hash = (hash * CharsToNameCanonicalizer.HASH_MULT) + i;
2017             // Ok, let's add char to output:
2018             outBuf[outPtr++] = c;
2019
2020             // Need more room?
2021             if (outPtr >= outBuf.length) {
2022                 outBuf = _textBuffer.finishCurrentSegment();
2023                 outPtr = 0;
2024             }
2025         }
2026         _textBuffer.setCurrentLength(outPtr);
2027         {
2028             TextBuffer tb = _textBuffer;
2029             char[] buf = tb.getTextBuffer();
2030             int start = tb.getTextOffset();
2031             int len = tb.size();
2032
2033             return _symbols.findSymbol(buf, start, len, hash);
2034         }
2035     }
2036
2037     @Override
2038     protected final void _finishString() throws IOException
2039     {
2040         /* First: let's try to see if we have simple String value: one
2041          * that does not cross input buffer boundary, and does not
2042          * contain escape sequences.
2043          */

2044         int ptr = _inputPtr;
2045         final int inputLen = _inputEnd;
2046
2047         if (ptr < inputLen) {
2048             final int[] codes = _icLatin1;
2049             final int maxCode = codes.length;
2050
2051             do {
2052                 int ch = _inputBuffer[ptr];
2053                 if (ch < maxCode && codes[ch] != 0) {
2054                     if (ch == '"') {
2055                         _textBuffer.resetWithShared(_inputBuffer, _inputPtr, (ptr-_inputPtr));
2056                         _inputPtr = ptr+1;
2057                         // Yes, we got it all
2058                         return;
2059                     }
2060                     break;
2061                 }
2062                 ++ptr;
2063             } while (ptr < inputLen);
2064         }
2065
2066         // Either ran out of input, or bumped into an escape sequence...
2067         _textBuffer.resetWithCopy(_inputBuffer, _inputPtr, (ptr-_inputPtr));
2068         _inputPtr = ptr;
2069         _finishString2();
2070     }
2071
2072     protected void _finishString2() throws IOException
2073     {
2074         char[] outBuf = _textBuffer.getCurrentSegment();
2075         int outPtr = _textBuffer.getCurrentSegmentSize();
2076         final int[] codes = _icLatin1;
2077         final int maxCode = codes.length;
2078
2079         while (true) {
2080             if (_inputPtr >= _inputEnd) {
2081                 if (!_loadMore()) {
2082                     _reportInvalidEOF(": was expecting closing quote for a string value",
2083                             JsonToken.VALUE_STRING);
2084                 }
2085             }
2086             char c = _inputBuffer[_inputPtr++];
2087             int i = (int) c;
2088             if (i < maxCode && codes[i] != 0) {
2089                 if (i == INT_QUOTE) {
2090                     break;
2091                 } else if (i == INT_BACKSLASH) {
2092                     /* Although chars outside of BMP are to be escaped as
2093                      * an UTF-16 surrogate pair, does that affect decoding?
2094                      * For now let's assume it does not.
2095                      */

2096                     c = _decodeEscaped();
2097                 } else if (i < INT_SPACE) {
2098                     _throwUnquotedSpace(i, "string value");
2099                 } // anything else?
2100             }
2101             // Need more room?
2102             if (outPtr >= outBuf.length) {
2103                 outBuf = _textBuffer.finishCurrentSegment();
2104                 outPtr = 0;
2105             }
2106             // Ok, let's add char to output:
2107             outBuf[outPtr++] = c;
2108         }
2109         _textBuffer.setCurrentLength(outPtr);
2110     }
2111
2112     /**
2113      * Method called to skim through rest of unparsed String value,
2114      * if it is not needed. This can be done bit faster if contents
2115      * need not be stored for future access.
2116      */

2117     protected final void _skipString() throws IOException
2118     {
2119         _tokenIncomplete = false;
2120
2121         int inPtr = _inputPtr;
2122         int inLen = _inputEnd;
2123         char[] inBuf = _inputBuffer;
2124
2125         while (true) {
2126             if (inPtr >= inLen) {
2127                 _inputPtr = inPtr;
2128                 if (!_loadMore()) {
2129                     _reportInvalidEOF(": was expecting closing quote for a string value",
2130                             JsonToken.VALUE_STRING);
2131                 }
2132                 inPtr = _inputPtr;
2133                 inLen = _inputEnd;
2134             }
2135             char c = inBuf[inPtr++];
2136             int i = (int) c;
2137             if (i <= INT_BACKSLASH) {
2138                 if (i == INT_BACKSLASH) {
2139                     // Although chars outside of BMP are to be escaped as an UTF-16 surrogate pair,
2140                     // does that affect decoding? For now let's assume it does not.
2141                     _inputPtr = inPtr;
2142                     /*c = */ _decodeEscaped();
2143                     inPtr = _inputPtr;
2144                     inLen = _inputEnd;
2145                 } else if (i <= INT_QUOTE) {
2146                     if (i == INT_QUOTE) {
2147                         _inputPtr = inPtr;
2148                         break;
2149                     }
2150                     if (i < INT_SPACE) {
2151                         _inputPtr = inPtr;
2152                         _throwUnquotedSpace(i, "string value");
2153                     }
2154                 }
2155             }
2156         }
2157     }
2158
2159     /*
2160     /**********************************************************
2161     /* Internal methods, other parsing
2162     /**********************************************************
2163      */

2164
2165     /**
2166      * We actually need to check the character value here
2167      * (to see if we have \n following \r).
2168      */

2169     protected final void _skipCR() throws IOException {
2170         if (_inputPtr < _inputEnd || _loadMore()) {
2171             if (_inputBuffer[_inputPtr] == '\n') {
2172                 ++_inputPtr;
2173             }
2174         }
2175         ++_currInputRow;
2176         _currInputRowStart = _inputPtr;
2177     }
2178
2179     private final int _skipColon() throws IOException
2180     {
2181         if ((_inputPtr + 4) >= _inputEnd) {
2182             return _skipColon2(false);
2183         }
2184         char c = _inputBuffer[_inputPtr];
2185         if (c == ':') { // common case, no leading space
2186             int i = _inputBuffer[++_inputPtr];
2187             if (i > INT_SPACE) { // nor trailing
2188                 if (i == INT_SLASH || i == INT_HASH) {
2189                     return _skipColon2(true);
2190                 }
2191                 ++_inputPtr;
2192                 return i;
2193             }
2194             if (i == INT_SPACE || i == INT_TAB) {
2195                 i = (int) _inputBuffer[++_inputPtr];
2196                 if (i > INT_SPACE) {
2197                     if (i == INT_SLASH || i == INT_HASH) {
2198                         return _skipColon2(true);
2199                     }
2200                     ++_inputPtr;
2201                     return i;
2202                 }
2203             }
2204             return _skipColon2(true); // true -> skipped colon
2205         }
2206         if (c == ' ' || c == '\t') {
2207             c = _inputBuffer[++_inputPtr];
2208         }
2209         if (c == ':') {
2210             int i = _inputBuffer[++_inputPtr];
2211             if (i > INT_SPACE) {
2212                 if (i == INT_SLASH || i == INT_HASH) {
2213                     return _skipColon2(true);
2214                 }
2215                 ++_inputPtr;
2216                 return i;
2217             }
2218             if (i == INT_SPACE || i == INT_TAB) {
2219                 i = (int) _inputBuffer[++_inputPtr];
2220                 if (i > INT_SPACE) {
2221                     if (i == INT_SLASH || i == INT_HASH) {
2222                         return _skipColon2(true);
2223                     }
2224                     ++_inputPtr;
2225                     return i;
2226                 }
2227             }
2228             return _skipColon2(true);
2229         }
2230         return _skipColon2(false);
2231     }
2232
2233     private final int _skipColon2(boolean gotColon) throws IOException
2234     {
2235         while (_inputPtr < _inputEnd || _loadMore()) {
2236             int i = (int) _inputBuffer[_inputPtr++];
2237             if (i > INT_SPACE) {
2238                 if (i == INT_SLASH) {
2239                     _skipComment();
2240                     continue;
2241                 }
2242                 if (i == INT_HASH) {
2243                     if (_skipYAMLComment()) {
2244                         continue;
2245                     }
2246                 }
2247                 if (gotColon) {
2248                     return i;
2249                 }
2250                 if (i != INT_COLON) {
2251                     _reportUnexpectedChar(i, "was expecting a colon to separate field name and value");
2252                 }
2253                 gotColon = true;
2254                 continue;
2255             }
2256             if (i < INT_SPACE) {
2257                 if (i == INT_LF) {
2258                     ++_currInputRow;
2259                     _currInputRowStart = _inputPtr;
2260                 } else if (i == INT_CR) {
2261                     _skipCR();
2262                 } else if (i != INT_TAB) {
2263                     _throwInvalidSpace(i);
2264                 }
2265             }
2266         }
2267         _reportInvalidEOF(" within/between "+_parsingContext.typeDesc()+" entries",
2268                 null);
2269         return -1;
2270     }
2271
2272     // Variant called when we know there's at least 4 more bytes available
2273     private final int _skipColonFast(int ptr) throws IOException
2274     {
2275         int i = (int) _inputBuffer[ptr++];
2276         if (i == INT_COLON) { // common case, no leading space
2277             i = _inputBuffer[ptr++];
2278             if (i > INT_SPACE) { // nor trailing
2279                 if (i != INT_SLASH && i != INT_HASH) {
2280                     _inputPtr = ptr;
2281                     return i;
2282                 }
2283             } else if (i == INT_SPACE || i == INT_TAB) {
2284                 i = (int) _inputBuffer[ptr++];
2285                 if (i > INT_SPACE) {
2286                     if (i != INT_SLASH && i != INT_HASH) {
2287                         _inputPtr = ptr;
2288                         return i;
2289                     }
2290                 }
2291             }
2292             _inputPtr = ptr-1;
2293             return _skipColon2(true); // true -> skipped colon
2294         }
2295         if (i == INT_SPACE || i == INT_TAB) {
2296             i = _inputBuffer[ptr++];
2297         }
2298         boolean gotColon = (i == INT_COLON);
2299         if (gotColon) {
2300             i = _inputBuffer[ptr++];
2301             if (i > INT_SPACE) {
2302                 if (i != INT_SLASH && i != INT_HASH) {
2303                     _inputPtr = ptr;
2304                     return i;
2305                 }
2306             } else if (i == INT_SPACE || i == INT_TAB) {
2307                 i = (int) _inputBuffer[ptr++];
2308                 if (i > INT_SPACE) {
2309                     if (i != INT_SLASH && i != INT_HASH) {
2310                         _inputPtr = ptr;
2311                         return i;
2312                     }
2313                 }
2314             }
2315         }
2316         _inputPtr = ptr-1;
2317         return _skipColon2(gotColon);
2318     }
2319
2320     // Primary loop: no reloading, comment handling
2321     private final int _skipComma(int i) throws IOException
2322     {
2323         if (i != INT_COMMA) {
2324             _reportUnexpectedChar(i, "was expecting comma to separate "+_parsingContext.typeDesc()+" entries");
2325         }
2326         while (_inputPtr < _inputEnd) {
2327             i = (int) _inputBuffer[_inputPtr++];
2328             if (i > INT_SPACE) {
2329                 if (i == INT_SLASH || i == INT_HASH) {
2330                     --_inputPtr;
2331                     return _skipAfterComma2();
2332                 }
2333                 return i;
2334             }
2335             if (i < INT_SPACE) {
2336                 if (i == INT_LF) {
2337                     ++_currInputRow;
2338                     _currInputRowStart = _inputPtr;
2339                 } else if (i == INT_CR) {
2340                     _skipCR();
2341                 } else if (i != INT_TAB) {
2342                     _throwInvalidSpace(i);
2343                 }
2344             }
2345         }
2346         return _skipAfterComma2();
2347     }
2348
2349     private final int _skipAfterComma2() throws IOException
2350     {
2351         while (_inputPtr < _inputEnd || _loadMore()) {
2352             int i = (int) _inputBuffer[_inputPtr++];
2353             if (i > INT_SPACE) {
2354                 if (i == INT_SLASH) {
2355                     _skipComment();
2356                     continue;
2357                 }
2358                 if (i == INT_HASH) {
2359                     if (_skipYAMLComment()) {
2360                         continue;
2361                     }
2362                 }
2363                 return i;
2364             }
2365             if (i < INT_SPACE) {
2366                 if (i == INT_LF) {
2367                     ++_currInputRow;
2368                     _currInputRowStart = _inputPtr;
2369                 } else if (i == INT_CR) {
2370                     _skipCR();
2371                 } else if (i != INT_TAB) {
2372                     _throwInvalidSpace(i);
2373                 }
2374             }
2375         }
2376         throw _constructError("Unexpected end-of-input within/between "+_parsingContext.typeDesc()+" entries");
2377     }
2378
2379     private final int _skipWSOrEnd() throws IOException
2380     {
2381         // Let's handle first character separately since it is likely that
2382         // it is either non-whitespace; or we have longer run of white space
2383         if (_inputPtr >= _inputEnd) {
2384             if (!_loadMore()) {
2385                 return _eofAsNextChar();
2386             }
2387         }
2388         int i = _inputBuffer[_inputPtr++];
2389         if (i > INT_SPACE) {
2390             if (i == INT_SLASH || i == INT_HASH) {
2391                 --_inputPtr;
2392                 return _skipWSOrEnd2();
2393             }
2394             return i;
2395         }
2396         if (i != INT_SPACE) {
2397             if (i == INT_LF) {
2398                 ++_currInputRow;
2399                 _currInputRowStart = _inputPtr;
2400             } else if (i == INT_CR) {
2401                 _skipCR();
2402             } else if (i != INT_TAB) {
2403                 _throwInvalidSpace(i);
2404             }
2405         }
2406
2407         while (_inputPtr < _inputEnd) {
2408             i = (int) _inputBuffer[_inputPtr++];
2409             if (i > INT_SPACE) {
2410                 if (i == INT_SLASH || i == INT_HASH) {
2411                     --_inputPtr;
2412                     return _skipWSOrEnd2();
2413                 }
2414                 return i;
2415             }
2416             if (i != INT_SPACE) {
2417                 if (i == INT_LF) {
2418                     ++_currInputRow;
2419                     _currInputRowStart = _inputPtr;
2420                 } else if (i == INT_CR) {
2421                     _skipCR();
2422                 } else if (i != INT_TAB) {
2423                     _throwInvalidSpace(i);
2424                 }
2425             }
2426         }
2427         return _skipWSOrEnd2();
2428     }
2429
2430     private int _skipWSOrEnd2() throws IOException
2431     {
2432         while (true) {
2433             if (_inputPtr >= _inputEnd) {
2434                 if (!_loadMore()) { // We ran out of input...
2435                     return _eofAsNextChar();
2436                 }
2437             }
2438             int i = (int) _inputBuffer[_inputPtr++];
2439             if (i > INT_SPACE) {
2440                 if (i == INT_SLASH) {
2441                     _skipComment();
2442                     continue;
2443                 }
2444                 if (i == INT_HASH) {
2445                     if (_skipYAMLComment()) {
2446                         continue;
2447                     }
2448                 }
2449                 return i;
2450             } else if (i != INT_SPACE) {
2451                 if (i == INT_LF) {
2452                     ++_currInputRow;
2453                     _currInputRowStart = _inputPtr;
2454                 } else if (i == INT_CR) {
2455                     _skipCR();
2456                 } else if (i != INT_TAB) {
2457                     _throwInvalidSpace(i);
2458                 }
2459             }
2460         }
2461     }
2462
2463     private void _skipComment() throws IOException
2464     {
2465         if ((_features & FEAT_MASK_ALLOW_JAVA_COMMENTS) == 0) {
2466             _reportUnexpectedChar('/', "maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)");
2467         }
2468         // First: check which comment (if either) it is:
2469         if (_inputPtr >= _inputEnd && !_loadMore()) {
2470             _reportInvalidEOF(" in a comment"null);
2471         }
2472         char c = _inputBuffer[_inputPtr++];
2473         if (c == '/') {
2474             _skipLine();
2475         } else if (c == '*') {
2476             _skipCComment();
2477         } else {
2478             _reportUnexpectedChar(c, "was expecting either '*' or '/' for a comment");
2479         }
2480     }
2481
2482     private void _skipCComment() throws IOException
2483     {
2484         // Ok: need the matching '*/'
2485         while ((_inputPtr < _inputEnd) || _loadMore()) {
2486             int i = (int) _inputBuffer[_inputPtr++];
2487             if (i <= '*') {
2488                 if (i == '*') { // end?
2489                     if ((_inputPtr >= _inputEnd) && !_loadMore()) {
2490                         break;
2491                     }
2492                     if (_inputBuffer[_inputPtr] == INT_SLASH) {
2493                         ++_inputPtr;
2494                         return;
2495                     }
2496                     continue;
2497                 }
2498                 if (i < INT_SPACE) {
2499                     if (i == INT_LF) {
2500                         ++_currInputRow;
2501                         _currInputRowStart = _inputPtr;
2502                     } else if (i == INT_CR) {
2503                         _skipCR();
2504                     } else if (i != INT_TAB) {
2505                         _throwInvalidSpace(i);
2506                     }
2507                 }
2508             }
2509         }
2510         _reportInvalidEOF(" in a comment"null);
2511     }
2512
2513     private boolean _skipYAMLComment() throws IOException
2514     {
2515         if ((_features & FEAT_MASK_ALLOW_YAML_COMMENTS) == 0) {
2516             return false;
2517         }
2518         _skipLine();
2519         return true;
2520     }
2521
2522     private void _skipLine() throws IOException
2523     {
2524         // Ok: need to find EOF or linefeed
2525         while ((_inputPtr < _inputEnd) || _loadMore()) {
2526             int i = (int) _inputBuffer[_inputPtr++];
2527             if (i < INT_SPACE) {
2528                 if (i == INT_LF) {
2529                     ++_currInputRow;
2530                     _currInputRowStart = _inputPtr;
2531                     break;
2532                 } else if (i == INT_CR) {
2533                     _skipCR();
2534                     break;
2535                 } else if (i != INT_TAB) {
2536                     _throwInvalidSpace(i);
2537                 }
2538             }
2539         }
2540     }
2541
2542     @Override
2543     protected char _decodeEscaped() throws IOException
2544     {
2545         if (_inputPtr >= _inputEnd) {
2546             if (!_loadMore()) {
2547                 _reportInvalidEOF(" in character escape sequence", JsonToken.VALUE_STRING);
2548             }
2549         }
2550         char c = _inputBuffer[_inputPtr++];
2551
2552         switch ((int) c) {
2553             // First, ones that are mapped
2554         case 'b':
2555             return '\b';
2556         case 't':
2557             return '\t';
2558         case 'n':
2559             return '\n';
2560         case 'f':
2561             return '\f';
2562         case 'r':
2563             return '\r';
2564
2565             // And these are to be returned as they are
2566         case '"':
2567         case '/':
2568         case '\\':
2569             return c;
2570
2571         case 'u': // and finally hex-escaped
2572             break;
2573
2574         default:
2575             return _handleUnrecognizedCharacterEscape(c);
2576         }
2577
2578         // Ok, a hex escape. Need 4 characters
2579         int value = 0;
2580         for (int i = 0; i < 4; ++i) {
2581             if (_inputPtr >= _inputEnd) {
2582                 if (!_loadMore()) {
2583                     _reportInvalidEOF(" in character escape sequence", JsonToken.VALUE_STRING);
2584                 }
2585             }
2586             int ch = (int) _inputBuffer[_inputPtr++];
2587             int digit = CharTypes.charToHex(ch);
2588             if (digit < 0) {
2589                 _reportUnexpectedChar(ch, "expected a hex-digit for character escape sequence");
2590             }
2591             value = (value << 4) | digit;
2592         }
2593         return (char) value;
2594     }
2595
2596     private final void _matchTrue() throws IOException {
2597         int ptr = _inputPtr;
2598         if ((ptr + 3) < _inputEnd) {
2599             final char[] b = _inputBuffer;
2600             if (b[ptr] == 'r' && b[++ptr] == 'u' && b[++ptr] == 'e') {
2601                 char c = b[++ptr];
2602                 if (c < '0' || c == ']' || c == '}') { // expected/allowed chars
2603                     _inputPtr = ptr;
2604                     return;
2605                 }
2606             }
2607         }
2608         // buffer boundary, or problem, offline
2609         _matchToken("true", 1);
2610     }
2611
2612     private final void _matchFalse() throws IOException {
2613         int ptr = _inputPtr;
2614         if ((ptr + 4) < _inputEnd) {
2615             final char[] b = _inputBuffer;
2616             if (b[ptr] == 'a' && b[++ptr] == 'l' && b[++ptr] == 's' && b[++ptr] == 'e') {
2617                 char c = b[++ptr];
2618                 if (c < '0' || c == ']' || c == '}') { // expected/allowed chars
2619                     _inputPtr = ptr;
2620                     return;
2621                 }
2622             }
2623         }
2624         // buffer boundary, or problem, offline
2625         _matchToken("false", 1);
2626     }
2627
2628     private final void _matchNull() throws IOException {
2629         int ptr = _inputPtr;
2630         if ((ptr + 3) < _inputEnd) {
2631             final char[] b = _inputBuffer;
2632             if (b[ptr] == 'u' && b[++ptr] == 'l' && b[++ptr] == 'l') {
2633                 char c = b[++ptr];
2634                 if (c < '0' || c == ']' || c == '}') { // expected/allowed chars
2635                     _inputPtr = ptr;
2636                     return;
2637                 }
2638             }
2639         }
2640         // buffer boundary, or problem, offline
2641         _matchToken("null", 1);
2642     }
2643
2644     /**
2645      * Helper method for checking whether input matches expected token
2646      */

2647     protected final void _matchToken(String matchStr, int i) throws IOException
2648     {
2649         final int len = matchStr.length();
2650         if ((_inputPtr + len) >= _inputEnd) {
2651             _matchToken2(matchStr, i);
2652             return;
2653         }
2654
2655         do {
2656             if (_inputBuffer[_inputPtr] != matchStr.charAt(i)) {
2657                 _reportInvalidToken(matchStr.substring(0, i));
2658             }
2659             ++_inputPtr;
2660         } while (++i < len);
2661         int ch = _inputBuffer[_inputPtr];
2662         if (ch >= '0' && ch != ']' && ch != '}') { // expected/allowed chars
2663             _checkMatchEnd(matchStr, i, ch);
2664         }
2665     }
2666
2667     private final void _matchToken2(String matchStr, int i) throws IOException
2668     {
2669         final int len = matchStr.length();
2670         do {
2671             if (((_inputPtr >= _inputEnd) && !_loadMore())
2672                 ||  (_inputBuffer[_inputPtr] != matchStr.charAt(i))) {
2673                 _reportInvalidToken(matchStr.substring(0, i));
2674             }
2675             ++_inputPtr;
2676         } while (++i < len);
2677     
2678         // but let's also ensure we either get EOF, or non-alphanum char...
2679         if (_inputPtr >= _inputEnd && !_loadMore()) {
2680             return;
2681         }
2682         int ch = _inputBuffer[_inputPtr];
2683         if (ch >= '0' && ch != ']' && ch != '}') { // expected/allowed chars
2684             _checkMatchEnd(matchStr, i, ch);
2685         }
2686     }
2687
2688     private final void _checkMatchEnd(String matchStr, int i, int c) throws IOException {
2689         // but actually only alphanums are problematic
2690         char ch = (char) c;
2691         if (Character.isJavaIdentifierPart(ch)) {
2692             _reportInvalidToken(matchStr.substring(0, i));
2693         }
2694     }
2695
2696     /*
2697     /**********************************************************
2698     /* Binary access
2699     /**********************************************************
2700      */

2701
2702     /**
2703      * Efficient handling for incremental parsing of base64-encoded
2704      * textual content.
2705      */

2706     @SuppressWarnings("resource")
2707     protected byte[] _decodeBase64(Base64Variant b64variant) throws IOException
2708     {
2709         ByteArrayBuilder builder = _getByteArrayBuilder();
2710
2711         //main_loop:
2712         while (true) {
2713             // first, we'll skip preceding white space, if any
2714             char ch;
2715             do {
2716                 if (_inputPtr >= _inputEnd) {
2717                     _loadMoreGuaranteed();
2718                 }
2719                 ch = _inputBuffer[_inputPtr++];
2720             } while (ch <= INT_SPACE);
2721             int bits = b64variant.decodeBase64Char(ch);
2722             if (bits < 0) {
2723                 if (ch == '"') { // reached the end, fair and square?
2724                     return builder.toByteArray();
2725                 }
2726                 bits = _decodeBase64Escape(b64variant, ch, 0);
2727                 if (bits < 0) { // white space to skip
2728                     continue;
2729                 }
2730             }
2731             int decodedData = bits;
2732
2733             // then second base64 char; can't get padding yet, nor ws
2734
2735             if (_inputPtr >= _inputEnd) {
2736                 _loadMoreGuaranteed();
2737             }
2738             ch = _inputBuffer[_inputPtr++];
2739             bits = b64variant.decodeBase64Char(ch);
2740             if (bits < 0) {
2741                 bits = _decodeBase64Escape(b64variant, ch, 1);
2742             }
2743             decodedData = (decodedData << 6) | bits;
2744
2745             // third base64 char; can be padding, but not ws
2746             if (_inputPtr >= _inputEnd) {
2747                 _loadMoreGuaranteed();
2748             }
2749             ch = _inputBuffer[_inputPtr++];
2750             bits = b64variant.decodeBase64Char(ch);
2751
2752             // First branch: can get padding (-> 1 byte)
2753             if (bits < 0) {
2754                 if (bits != Base64Variant.BASE64_VALUE_PADDING) {
2755                     // as per [JACKSON-631], could also just be 'missing'  padding
2756                     if (ch == '"') {
2757                         decodedData >>= 4;
2758                         builder.append(decodedData);
2759                         if (b64variant.usesPadding()) {
2760                             --_inputPtr; // to keep parser state bit more consistent
2761                             _handleBase64MissingPadding(b64variant);
2762                         }
2763                         return builder.toByteArray();
2764                     }
2765                     bits = _decodeBase64Escape(b64variant, ch, 2);
2766                 }
2767                 if (bits == Base64Variant.BASE64_VALUE_PADDING) {
2768                     // Ok, must get more padding chars, then
2769                     if (_inputPtr >= _inputEnd) {
2770                         _loadMoreGuaranteed();
2771                     }
2772                     ch = _inputBuffer[_inputPtr++];
2773                     if (!b64variant.usesPaddingChar(ch)) {
2774                         if (_decodeBase64Escape(b64variant, ch, 3) != Base64Variant.BASE64_VALUE_PADDING) {
2775                             throw reportInvalidBase64Char(b64variant, ch, 3, "expected padding character '"+b64variant.getPaddingChar()+"'");
2776                         }
2777                     }
2778                     // Got 12 bits, only need 8, need to shift
2779                     decodedData >>= 4;
2780                     builder.append(decodedData);
2781                     continue;
2782                 }
2783                 // otherwise we got escaped other char, to be processed below
2784             }
2785             // Nope, 2 or 3 bytes
2786             decodedData = (decodedData << 6) | bits;
2787             // fourth and last base64 char; can be padding, but not ws
2788             if (_inputPtr >= _inputEnd) {
2789                 _loadMoreGuaranteed();
2790             }
2791             ch = _inputBuffer[_inputPtr++];
2792             bits = b64variant.decodeBase64Char(ch);
2793             if (bits < 0) {
2794                 if (bits != Base64Variant.BASE64_VALUE_PADDING) {
2795                     // as per [JACKSON-631], could also just be 'missing'  padding
2796                     if (ch == '"') {
2797                         decodedData >>= 2;
2798                         builder.appendTwoBytes(decodedData);
2799                         if (b64variant.usesPadding()) {
2800                             --_inputPtr; // to keep parser state bit more consistent
2801                             _handleBase64MissingPadding(b64variant);
2802                         }
2803                         return builder.toByteArray();
2804                     }
2805                     bits = _decodeBase64Escape(b64variant, ch, 3);
2806                 }
2807                 if (bits == Base64Variant.BASE64_VALUE_PADDING) {
2808                     // With padding we only get 2 bytes; but we have
2809                     // to shift it a bit so it is identical to triplet
2810                     // case with partial output.
2811                     // 3 chars gives 3x6 == 18 bits, of which 2 are
2812                     // dummies, need to discard:
2813                     decodedData >>= 2;
2814                     builder.appendTwoBytes(decodedData);
2815                     continue;
2816                 }
2817                 // otherwise we got escaped other char, to be processed below
2818             }
2819             // otherwise, our triplet is now complete
2820             decodedData = (decodedData << 6) | bits;
2821             builder.appendThreeBytes(decodedData);
2822         }
2823     }
2824
2825     /*
2826     /**********************************************************
2827     /* Internal methods, location updating (refactored in 2.7)
2828     /**********************************************************
2829      */

2830
2831     @Override
2832     public JsonLocation getTokenLocation()
2833     {
2834         if (_currToken == JsonToken.FIELD_NAME) {
2835             long total = _currInputProcessed + (_nameStartOffset-1);
2836             return new JsonLocation(_getSourceReference(),
2837                     -1L, total, _nameStartRow, _nameStartCol);
2838         }
2839         return new JsonLocation(_getSourceReference(),
2840                 -1L, _tokenInputTotal-1, _tokenInputRow, _tokenInputCol);
2841     }
2842
2843     @Override
2844     public JsonLocation getCurrentLocation() {
2845         final int col = _inputPtr - _currInputRowStart + 1; // 1-based
2846         return new JsonLocation(_getSourceReference(),
2847                 -1L, _currInputProcessed + _inputPtr,
2848                 _currInputRow, col);
2849     }
2850
2851     // @since 2.7
2852     private final void _updateLocation()
2853     {
2854         int ptr = _inputPtr;
2855         _tokenInputTotal = _currInputProcessed + ptr;
2856         _tokenInputRow = _currInputRow;
2857         _tokenInputCol = ptr - _currInputRowStart;
2858     }
2859
2860     // @since 2.7
2861     private final void _updateNameLocation()
2862     {
2863         int ptr = _inputPtr;
2864         _nameStartOffset = ptr;
2865         _nameStartRow = _currInputRow;
2866         _nameStartCol = ptr - _currInputRowStart;
2867     }
2868
2869     /*
2870     /**********************************************************
2871     /* Error reporting
2872     /**********************************************************
2873      */

2874
2875     protected void _reportInvalidToken(String matchedPart) throws IOException {
2876         _reportInvalidToken(matchedPart, _validJsonTokenList());
2877     }
2878
2879     protected void _reportInvalidToken(String matchedPart, String msg) throws IOException
2880     {
2881         /* Let's just try to find what appears to be the token, using
2882          * regular Java identifier character rules. It's just a heuristic,
2883          * nothing fancy here.
2884          */

2885         StringBuilder sb = new StringBuilder(matchedPart);
2886         while ((_inputPtr < _inputEnd) || _loadMore()) {
2887             char c = _inputBuffer[_inputPtr];
2888             if (!Character.isJavaIdentifierPart(c)) {
2889                 break;
2890             }
2891             ++_inputPtr;
2892             sb.append(c);
2893             if (sb.length() >= MAX_ERROR_TOKEN_LENGTH) {
2894                 sb.append("...");
2895                 break;
2896             }
2897         }
2898         _reportError("Unrecognized token '%s': was expecting %s", sb, msg);
2899     }
2900
2901     /*
2902     /**********************************************************
2903     /* Internal methods, other
2904     /**********************************************************
2905      */

2906
2907     private void _closeScope(int i) throws JsonParseException {
2908         if (i == INT_RBRACKET) {
2909             _updateLocation();
2910             if (!_parsingContext.inArray()) {
2911                 _reportMismatchedEndMarker(i, '}');
2912             }
2913             _parsingContext = _parsingContext.clearAndGetParent();
2914             _currToken = JsonToken.END_ARRAY;
2915         }
2916         if (i == INT_RCURLY) {
2917             _updateLocation();
2918             if (!_parsingContext.inObject()) {
2919                 _reportMismatchedEndMarker(i, ']');
2920             }
2921             _parsingContext = _parsingContext.clearAndGetParent();
2922             _currToken = JsonToken.END_OBJECT;
2923         }
2924     }
2925 }
2926