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