001/*
002 * Copyright 2016-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.util.json;
022
023
024
025import java.io.BufferedInputStream;
026import java.io.Closeable;
027import java.io.InputStream;
028import java.io.IOException;
029import java.nio.charset.StandardCharsets;
030import java.util.ArrayList;
031import java.util.LinkedHashMap;
032import java.util.Map;
033
034import com.unboundid.util.ByteStringBuffer;
035import com.unboundid.util.Debug;
036import com.unboundid.util.StaticUtils;
037import com.unboundid.util.ThreadSafety;
038import com.unboundid.util.ThreadSafetyLevel;
039
040import static com.unboundid.util.json.JSONMessages.*;
041
042
043
044/**
045 * This class provides a mechanism for reading JSON objects from an input
046 * stream.  It assumes that any non-ASCII data that may be read from the input
047 * stream is encoded as UTF-8.
048 */
049@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
050public final class JSONObjectReader
051       implements Closeable
052{
053  // The buffer used to hold the bytes of the object currently being read.
054  private final ByteStringBuffer currentObjectBytes;
055
056  // A buffer to use to hold strings being decoded.
057  private final ByteStringBuffer stringBuffer;
058
059  // The input stream from which JSON objects will be read.
060  private final InputStream inputStream;
061
062
063
064  /**
065   * Creates a new JSON object reader that will read objects from the provided
066   * input stream.
067   *
068   * @param  inputStream  The input stream from which the data should be read.
069   */
070  public JSONObjectReader(final InputStream inputStream)
071  {
072    this(inputStream, true);
073  }
074
075
076
077  /**
078   * Creates a new JSON object reader that will read objects from the provided
079   * input stream.
080   *
081   * @param  inputStream        The input stream from which the data should be
082   *                            read.
083   * @param  bufferInputStream  Indicates whether to buffer the input stream.
084   *                            This should be {@code false} if the input stream
085   *                            could be used for any purpose other than reading
086   *                            JSON objects after one or more objects are read.
087   */
088  public JSONObjectReader(final InputStream inputStream,
089                          final boolean bufferInputStream)
090  {
091    if (bufferInputStream && (! (inputStream instanceof BufferedInputStream)))
092    {
093      this.inputStream = new BufferedInputStream(inputStream);
094    }
095    else
096    {
097      this.inputStream = inputStream;
098    }
099
100    currentObjectBytes = new ByteStringBuffer();
101    stringBuffer = new ByteStringBuffer();
102  }
103
104
105
106  /**
107   * Reads the next JSON object from the input stream.
108   *
109   * @return  The JSON object that was read, or {@code null} if the end of the
110   *          end of the stream has been reached..
111   *
112   * @throws  IOException  If a problem is encountered while reading from the
113   *                       input stream.
114   *
115   * @throws  JSONException  If the data read
116   */
117  public JSONObject readObject()
118         throws IOException, JSONException
119  {
120    // Skip over any whitespace before the beginning of the next object.
121    skipWhitespace();
122    currentObjectBytes.clear();
123
124
125    // The JSON object must start with an open curly brace.
126    final Object firstToken = readToken(true);
127    if (firstToken == null)
128    {
129      return null;
130    }
131
132    if (! firstToken.equals('{'))
133    {
134      throw new JSONException(ERR_OBJECT_READER_ILLEGAL_START_OF_OBJECT.get(
135           String.valueOf(firstToken)));
136    }
137
138    final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10);
139    readObject(m);
140
141    return new JSONObject(m, currentObjectBytes.toString());
142  }
143
144
145
146  /**
147   * Closes this JSON object reader and the underlying input stream.
148   *
149   * @throws  IOException  If a problem is encountered while closing the
150   *                       underlying input stream.
151   */
152  @Override()
153  public void close()
154         throws IOException
155  {
156    inputStream.close();
157  }
158
159
160
161  /**
162   * Reads a token from the input stream, skipping over any insignificant
163   * whitespace that may be before the token.  The token that is returned will
164   * be one of the following:
165   * <UL>
166   *   <LI>A {@code Character} that is an opening curly brace.</LI>
167   *   <LI>A {@code Character} that is a closing curly brace.</LI>
168   *   <LI>A {@code Character} that is an opening square bracket.</LI>
169   *   <LI>A {@code Character} that is a closing square bracket.</LI>
170   *   <LI>A {@code Character} that is a colon.</LI>
171   *   <LI>A {@code Character} that is a comma.</LI>
172   *   <LI>A {@link JSONBoolean}.</LI>
173   *   <LI>A {@link JSONNull}.</LI>
174   *   <LI>A {@link JSONNumber}.</LI>
175   *   <LI>A {@link JSONString}.</LI>
176   * </UL>
177   *
178   * @param  allowEndOfStream  Indicates whether it is acceptable to encounter
179   *                           the end of the input stream.  This should only
180   *                           be {@code true} when the token is expected to be
181   *                           the open parenthesis of the outermost JSON
182   *                           object.
183   *
184   * @return  The token that was read, or {@code null} if the end of the input
185   *          stream was reached.
186   *
187   * @throws  IOException  If a problem is encountered while reading from the
188   *                       input stream.
189   *
190   * @throws  JSONException  If a problem was encountered while reading the
191   *                         token.
192   */
193  private Object readToken(final boolean allowEndOfStream)
194          throws IOException, JSONException
195  {
196    skipWhitespace();
197
198    final Byte byteRead = readByte(allowEndOfStream);
199    if (byteRead == null)
200    {
201      return null;
202    }
203
204    switch (byteRead)
205    {
206      case '{':
207        return '{';
208      case '}':
209        return '}';
210      case '[':
211        return '[';
212      case ']':
213        return ']';
214      case ':':
215        return ':';
216      case ',':
217        return ',';
218
219      case '"':
220        // This is the start of a JSON string.
221        return readString();
222
223      case 't':
224      case 'f':
225        // This is the start of a JSON true or false value.
226        return readBoolean();
227
228      case 'n':
229        // This is the start of a JSON null value.
230        return readNull();
231
232      case '-':
233      case '0':
234      case '1':
235      case '2':
236      case '3':
237      case '4':
238      case '5':
239      case '6':
240      case '7':
241      case '8':
242      case '9':
243        // This is the start of a JSON number value.
244        return readNumber();
245
246      default:
247        throw new JSONException(
248             ERR_OBJECT_READER_ILLEGAL_FIRST_CHAR_FOR_JSON_TOKEN.get(
249                  currentObjectBytes.length(), byteToCharString(byteRead)));
250    }
251  }
252
253
254
255  /**
256   * Skips over any valid JSON whitespace at the current position in the input
257   * stream.
258   *
259   * @throws  IOException  If a problem is encountered while reading from the
260   *                       input stream.
261   *
262   * @throws  JSONException  If a problem is encountered while skipping
263   *                         whitespace.
264   */
265  private void skipWhitespace()
266          throws IOException, JSONException
267  {
268    while (true)
269    {
270      inputStream.mark(1);
271      final Byte byteRead = readByte(true);
272      if (byteRead == null)
273      {
274        // We've reached the end of the input stream.
275        return;
276      }
277
278      switch (byteRead)
279      {
280        case ' ':
281        case '\t':
282        case '\n':
283        case '\r':
284          // Spaces, tabs, newlines, and carriage returns are valid JSON
285          // whitespace.
286          break;
287
288        // Technically, JSON does not provide support for comments.  But this
289        // implementation will accept three types of comments:
290        // - Comments that start with /* and end with */ (potentially spanning
291        //   multiple lines).
292        // - Comments that start with // and continue until the end of the line.
293        // - Comments that start with # and continue until the end of the line.
294        // All comments will be ignored by the parser.
295        case '/':
296          // This probably starts a comment.  If so, then the next byte must be
297          // either another forward slash or an asterisk.
298          final byte nextByte = readByte(false);
299          if (nextByte == '/')
300          {
301            // Keep reading until we encounter a newline, a carriage return, or
302            // the end of the input stream.
303            while (true)
304            {
305              final Byte commentByte = readByte(true);
306              if (commentByte == null)
307              {
308                return;
309              }
310
311              if ((commentByte == '\n') || (commentByte == '\r'))
312              {
313                break;
314              }
315            }
316          }
317          else if (nextByte == '*')
318          {
319            // Keep reading until we encounter an asterisk followed by a slash.
320            // If we hit the end of the input stream before that, then that's an
321            // error.
322            while (true)
323            {
324              final Byte commentByte = readByte(false);
325              if (commentByte == '*')
326              {
327                final Byte possibleSlashByte = readByte(false);
328                if (possibleSlashByte == '/')
329                {
330                  break;
331                }
332              }
333            }
334          }
335          else
336          {
337            throw new JSONException(
338                 ERR_OBJECT_READER_ILLEGAL_SLASH_SKIPPING_WHITESPACE.get(
339                      currentObjectBytes.length()));
340          }
341          break;
342
343        case '#':
344          // Keep reading until we encounter a newline, a carriage return, or
345          // the end of the input stream.
346          while (true)
347          {
348            final Byte commentByte = readByte(true);
349            if (commentByte == null)
350            {
351              return;
352            }
353
354            if ((commentByte == '\n') || (commentByte == '\r'))
355            {
356              break;
357            }
358          }
359          break;
360
361        default:
362          // We read a byte that isn't whitespace, so we'll need to reset the
363          // stream so it will be read again, and we'll also need to remove the
364          // that byte from the currentObjectBytes buffer.
365          inputStream.reset();
366          currentObjectBytes.setLength(currentObjectBytes.length() - 1);
367          return;
368      }
369    }
370  }
371
372
373
374  /**
375   * Reads the next byte from the input stream.
376   *
377   * @param  allowEndOfStream  Indicates whether it is acceptable to encounter
378   *                           the end of the input stream.  This should only
379   *                           be {@code true} when the token is expected to be
380   *                           the open parenthesis of the outermost JSON
381   *                           object.
382   *
383   * @return  The next byte read from the input stream, or {@code null} if the
384   *          end of the input stream has been reached and that is acceptable.
385   *
386   * @throws  IOException  If a problem is encountered while reading from the
387   *                       input stream.
388   *
389   * @throws  JSONException  If the end of the input stream is reached when that
390   *                         is not acceptable.
391   */
392  private Byte readByte(final boolean allowEndOfStream)
393          throws IOException, JSONException
394  {
395    final int byteRead = inputStream.read();
396    if (byteRead < 0)
397    {
398      if (allowEndOfStream)
399      {
400        return null;
401      }
402      else
403      {
404        throw new JSONException(ERR_OBJECT_READER_UNEXPECTED_END_OF_STREAM.get(
405             currentObjectBytes.length()));
406      }
407    }
408
409    final byte b = (byte) (byteRead & 0xFF);
410    currentObjectBytes.append(b);
411    return b;
412  }
413
414
415
416  /**
417   * Reads a string from the input stream.  The open quotation must have already
418   * been read.
419   *
420   * @return  The JSON string that was read.
421   *
422   * @throws  IOException  If a problem is encountered while reading from the
423   *                       input stream.
424   *
425   * @throws  JSONException  If a problem was encountered while reading the JSON
426   *                         string.
427   */
428  private JSONString readString()
429          throws IOException, JSONException
430  {
431    // Use a buffer to hold the string being decoded.  Also mark the current
432    // position in the bytes that comprise the string representation so that
433    // the JSON string representation (including the opening quote) will be
434    // exactly as it was provided.
435    stringBuffer.clear();
436    final int jsonStringStartPos = currentObjectBytes.length() - 1;
437    while (true)
438    {
439      final Byte byteRead = readByte(false);
440
441      // See if it's a non-ASCII byte.  If so, then assume that it's UTF-8 and
442      // read the appropriate number of remaining bytes.  We need to handle this
443      // specially to avoid incorrectly detecting the end of the string because
444      // a subsequent byte in a multi-byte character happens to be the same as
445      // the ASCII quotation mark byte.
446      if ((byteRead & 0x80) == 0x80)
447      {
448        final byte[] charBytes;
449        if ((byteRead & 0xE0) == 0xC0)
450        {
451          // It's a two-byte character.
452          charBytes = new byte[]
453          {
454            byteRead,
455            readByte(false)
456          };
457        }
458        else if ((byteRead & 0xF0) == 0xE0)
459        {
460          // It's a three-byte character.
461          charBytes = new byte[]
462          {
463            byteRead,
464            readByte(false),
465            readByte(false)
466          };
467        }
468        else if ((byteRead & 0xF8) == 0xF0)
469        {
470          // It's a four-byte character.
471          charBytes = new byte[]
472          {
473            byteRead,
474            readByte(false),
475            readByte(false),
476            readByte(false)
477          };
478        }
479        else
480        {
481          // This isn't a valid UTF-8 sequence.
482          throw new JSONException(
483               ERR_OBJECT_READER_INVALID_UTF_8_BYTE_IN_STREAM.get(
484                    currentObjectBytes.length(),
485                    "0x" + StaticUtils.toHex(byteRead)));
486        }
487
488        stringBuffer.append(new String(charBytes, StandardCharsets.UTF_8));
489        continue;
490      }
491
492
493      // If the byte that we read was an escape, then we know that whatever
494      // immediately follows it shouldn't be allowed to signal the end of the
495      // string.
496      if (byteRead == '\\')
497      {
498        final byte nextByte = readByte(false);
499        switch (nextByte)
500        {
501          case '"':
502          case '\\':
503          case '/':
504            stringBuffer.append(nextByte);
505            break;
506          case 'b':
507            stringBuffer.append('\b');
508            break;
509          case 'f':
510            stringBuffer.append('\f');
511            break;
512          case 'n':
513            stringBuffer.append('\n');
514            break;
515          case 'r':
516            stringBuffer.append('\r');
517            break;
518          case 't':
519            stringBuffer.append('\t');
520            break;
521          case 'u':
522            final char[] hexChars =
523            {
524              (char) (readByte(false) & 0xFF),
525              (char) (readByte(false) & 0xFF),
526              (char) (readByte(false) & 0xFF),
527              (char) (readByte(false) & 0xFF)
528            };
529
530            try
531            {
532              stringBuffer.append(
533                   (char) Integer.parseInt(new String(hexChars), 16));
534            }
535            catch (final Exception e)
536            {
537              Debug.debugException(e);
538              throw new JSONException(
539                   ERR_OBJECT_READER_INVALID_UNICODE_ESCAPE.get(
540                        currentObjectBytes.length()),
541                   e);
542            }
543            break;
544          default:
545            throw new JSONException(
546                 ERR_OBJECT_READER_INVALID_ESCAPED_CHAR.get(
547                      currentObjectBytes.length(), byteToCharString(nextByte)));
548        }
549        continue;
550      }
551
552      if (byteRead == '"')
553      {
554        // It's an unescaped quote, so it marks the end of the string.
555        return new JSONString(stringBuffer.toString(),
556             new String(currentObjectBytes.getBackingArray(),
557                  jsonStringStartPos,
558                  (currentObjectBytes.length() - jsonStringStartPos),
559                  StandardCharsets.UTF_8));
560      }
561
562      final int byteReadInt = (byteRead & 0xFF);
563      if ((byteRead & 0xFF) <= 0x1F)
564      {
565        throw new JSONException(ERR_OBJECT_READER_UNESCAPED_CONTROL_CHAR.get(
566             currentObjectBytes.length(), byteToCharString(byteRead)));
567      }
568      else
569      {
570        stringBuffer.append((char) byteReadInt);
571      }
572    }
573  }
574
575
576
577  /**
578   * Reads a JSON Boolean from the input stream.  The first byte of either 't'
579   * or 'f' will have already been read.
580   *
581   * @return  The JSON Boolean that was read.
582   *
583   * @throws  IOException  If a problem is encountered while reading from the
584   *                       input stream.
585   *
586   * @throws  JSONException  If a problem was encountered while reading the JSON
587   *                         Boolean.
588   */
589  private JSONBoolean readBoolean()
590          throws IOException, JSONException
591  {
592    final byte firstByte =
593         currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1];
594    if (firstByte == 't')
595    {
596      if ((readByte(false) == 'r') &&
597          (readByte(false) == 'u') &&
598          (readByte(false) == 'e'))
599      {
600        return JSONBoolean.TRUE;
601      }
602
603      throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_TRUE.get(
604           currentObjectBytes.length()));
605    }
606    else
607    {
608      if ((readByte(false) == 'a') &&
609          (readByte(false) == 'l') &&
610          (readByte(false) == 's') &&
611          (readByte(false) == 'e'))
612      {
613        return JSONBoolean.FALSE;
614      }
615
616      throw new JSONException(ERR_OBJECT_READER_INVALID_BOOLEAN_FALSE.get(
617           currentObjectBytes.length()));
618    }
619  }
620
621
622
623  /**
624   * Reads a JSON Boolean from the input stream.  The first byte of 'n' will
625   * have already been read.
626   *
627   * @return  The JSON null that was read.
628   *
629   * @throws  IOException  If a problem is encountered while reading from the
630   *                       input stream.
631   *
632   * @throws  JSONException  If a problem was encountered while reading the JSON
633   *                         null.
634   */
635  private JSONNull readNull()
636          throws IOException, JSONException
637  {
638    if ((readByte(false) == 'u') &&
639         (readByte(false) == 'l') &&
640         (readByte(false) == 'l'))
641    {
642      return JSONNull.NULL;
643    }
644
645    throw new JSONException(ERR_OBJECT_READER_INVALID_NULL.get(
646         currentObjectBytes.length()));
647  }
648
649
650
651  /**
652   * Reads a JSON number from the input stream.  The first byte of the number
653   * will have already been read.
654   *
655   * @throws  IOException  If a problem is encountered while reading from the
656   *                       input stream.
657   *
658   * @return  The JSON number that was read.
659   *
660   * @throws  IOException  If a problem is encountered while reading from the
661   *                       input stream.
662   *
663   * @throws  JSONException  If a problem was encountered while reading the JSON
664   *                         number.
665   */
666  private JSONNumber readNumber()
667          throws IOException, JSONException
668  {
669    // Use a buffer to hold the string representation of the number being
670    // decoded.  Since the first byte of the number has already been read, we'll
671    // need to add it into the buffer.
672    stringBuffer.clear();
673    stringBuffer.append(
674         currentObjectBytes.getBackingArray()[currentObjectBytes.length() - 1]);
675
676
677    // Read until we encounter whitespace, a comma, a closing square bracket, or
678    // a closing curly brace.  Then try to parse what we read as a number.
679    while (true)
680    {
681      // Mark the stream so that if we read a byte that isn't part of the
682      // number, we'll be able to rewind the stream so that byte will be read
683      // again by something else.
684      inputStream.mark(1);
685
686      final Byte b = readByte(false);
687      switch (b)
688      {
689        case ' ':
690        case '\t':
691        case '\n':
692        case '\r':
693        case ',':
694        case ']':
695        case '}':
696          // This tell us we're at the end of the number.  Rewind the stream so
697          // that we can read this last byte again whatever tries to get the
698          // next token.  Also remove it from the end of currentObjectBytes
699          // since it will be re-added when it's read again.
700          inputStream.reset();
701          currentObjectBytes.setLength(currentObjectBytes.length() - 1);
702          return new JSONNumber(stringBuffer.toString());
703
704        default:
705          stringBuffer.append(b);
706      }
707    }
708  }
709
710
711
712  /**
713   * Reads a JSON array from the input stream.  The opening square bracket will
714   * have already been read.
715   *
716   * @return  The JSON array that was read.
717   *
718   * @throws  IOException  If a problem is encountered while reading from the
719   *                       input stream.
720   *
721   * @throws  JSONException  If a problem was encountered while reading the JSON
722   *                         array.
723   */
724  private JSONArray readArray()
725          throws IOException, JSONException
726  {
727    // The opening square bracket will have already been consumed, so read
728    // JSON values until we hit a closing square bracket.
729    final ArrayList<JSONValue> values = new ArrayList<>(10);
730    boolean firstToken = true;
731    while (true)
732    {
733      // If this is the first time through, it is acceptable to find a closing
734      // square bracket.  Otherwise, we expect to find a JSON value, an opening
735      // square bracket to denote the start of an embedded array, or an opening
736      // curly brace to denote the start of an embedded JSON object.
737      final Object token = readToken(false);
738      if (token instanceof JSONValue)
739      {
740        values.add((JSONValue) token);
741      }
742      else if (token.equals('['))
743      {
744        values.add(readArray());
745      }
746      else if (token.equals('{'))
747      {
748        final LinkedHashMap<String,JSONValue> fieldMap =
749             new LinkedHashMap<>(10);
750        values.add(readObject(fieldMap));
751      }
752      else if (token.equals(']') && firstToken)
753      {
754        // It's an empty array.
755        return JSONArray.EMPTY_ARRAY;
756      }
757      else
758      {
759        throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_ARRAY.get(
760             currentObjectBytes.length(), String.valueOf(token)));
761      }
762
763      firstToken = false;
764
765
766      // If we've gotten here, then we found a JSON value.  It must be followed
767      // by either a comma (to indicate that there's at least one more value) or
768      // a closing square bracket (to denote the end of the array).
769      final Object nextToken = readToken(false);
770      if (nextToken.equals(']'))
771      {
772        return new JSONArray(values);
773      }
774      else if (! nextToken.equals(','))
775      {
776        throw new JSONException(
777             ERR_OBJECT_READER_INVALID_TOKEN_AFTER_ARRAY_VALUE.get(
778                  currentObjectBytes.length(), String.valueOf(nextToken)));
779      }
780    }
781  }
782
783
784
785  /**
786   * Reads a JSON object from the input stream.  The opening curly brace will
787   * have already been read.
788   *
789   * @param  fields  The map into which to place the fields that are read.  The
790   *                 returned object will include an unmodifiable view of this
791   *                 map, but the caller may use the map directly if desired.
792   *
793   * @return  The JSON object that was read.
794   *
795   * @throws  IOException  If a problem is encountered while reading from the
796   *                       input stream.
797   *
798   * @throws  JSONException  If a problem was encountered while reading the JSON
799   *                         object.
800   */
801  private JSONObject readObject(final Map<String,JSONValue> fields)
802          throws IOException, JSONException
803  {
804    boolean firstField = true;
805    while (true)
806    {
807      // Read the next token.  It must be a JSONString, unless we haven't read
808      // any fields yet in which case it can be a closing curly brace to
809      // indicate that it's an empty object.
810      final String fieldName;
811      final Object fieldNameToken = readToken(false);
812      if (fieldNameToken instanceof JSONString)
813      {
814        fieldName = ((JSONString) fieldNameToken).stringValue();
815        if (fields.containsKey(fieldName))
816        {
817          throw new JSONException(ERR_OBJECT_READER_DUPLICATE_FIELD.get(
818               currentObjectBytes.length(), fieldName));
819        }
820      }
821      else if (firstField && fieldNameToken.equals('}'))
822      {
823        return new JSONObject(fields);
824      }
825      else
826      {
827        throw new JSONException(ERR_OBJECT_READER_INVALID_TOKEN_IN_OBJECT.get(
828             currentObjectBytes.length(), String.valueOf(fieldNameToken)));
829      }
830      firstField = false;
831
832      // Read the next token.  It must be a colon.
833      final Object colonToken = readToken(false);
834      if (! colonToken.equals(':'))
835      {
836        throw new JSONException(ERR_OBJECT_READER_TOKEN_NOT_COLON.get(
837             currentObjectBytes.length(), String.valueOf(colonToken),
838             String.valueOf(fieldNameToken)));
839      }
840
841      // Read the next token.  It must be one of the following:
842      // - A JSONValue
843      // - An opening square bracket, designating the start of an array.
844      // - An opening curly brace, designating the start of an object.
845      final Object valueToken = readToken(false);
846      if (valueToken instanceof JSONValue)
847      {
848        fields.put(fieldName, (JSONValue) valueToken);
849      }
850      else if (valueToken.equals('['))
851      {
852        final JSONArray a = readArray();
853        fields.put(fieldName, a);
854      }
855      else if (valueToken.equals('{'))
856      {
857        final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10);
858        final JSONObject o = readObject(m);
859        fields.put(fieldName, o);
860      }
861      else
862      {
863        throw new JSONException(ERR_OBJECT_READER_TOKEN_NOT_VALUE.get(
864             currentObjectBytes.length(), String.valueOf(valueToken),
865             String.valueOf(fieldNameToken)));
866      }
867
868      // Read the next token.  It must be either a comma (to indicate that
869      // there will be another field) or a closing curly brace (to indicate
870      // that the end of the object has been reached).
871      final Object separatorToken = readToken(false);
872      if (separatorToken.equals('}'))
873      {
874        return new JSONObject(fields);
875      }
876      else if (! separatorToken.equals(','))
877      {
878        throw new JSONException(
879             ERR_OBJECT_READER_INVALID_TOKEN_AFTER_OBJECT_VALUE.get(
880                  currentObjectBytes.length(), String.valueOf(separatorToken),
881                  String.valueOf(fieldNameToken)));
882      }
883    }
884  }
885
886
887
888  /**
889   * Retrieves a string representation of the provided byte that is intended to
890   * represent a character.  If the provided byte is a printable ASCII
891   * character, then that character will be used.  Otherwise, the string
892   * representation will be "0x" followed by the hexadecimal representation of
893   * the byte.
894   *
895   * @param  b  The byte for which to obtain the string representation.
896   *
897   * @return  A string representation of the provided byte.
898   */
899  private static String byteToCharString(final byte b)
900  {
901    if ((b >= ' ') && (b <= '~'))
902    {
903      return String.valueOf((char) (b & 0xFF));
904    }
905    else
906    {
907      return "0x" + StaticUtils.toHex(b);
908    }
909  }
910}