001/*
002 * Copyright 2015-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-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.math.BigDecimal;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.Iterator;
030import java.util.LinkedHashMap;
031import java.util.List;
032import java.util.Map;
033import java.util.TreeMap;
034
035import com.unboundid.util.Debug;
036import com.unboundid.util.NotMutable;
037import com.unboundid.util.StaticUtils;
038import com.unboundid.util.ThreadSafety;
039import com.unboundid.util.ThreadSafetyLevel;
040
041import static com.unboundid.util.json.JSONMessages.*;
042
043
044
045/**
046 * This class provides an implementation of a JSON value that represents an
047 * object with zero or more name-value pairs.  In each pair, the name is a JSON
048 * string and the value is any type of JSON value ({@code null}, {@code true},
049 * {@code false}, number, string, array, or object).  Although the ECMA-404
050 * specification does not explicitly forbid a JSON object from having multiple
051 * fields with the same name, RFC 7159 section 4 states that field names should
052 * be unique, and this implementation does not support objects in which multiple
053 * fields have the same name.  Note that this uniqueness constraint only applies
054 * to the fields directly contained within an object, and does not prevent an
055 * object from having a field value that is an object (or that is an array
056 * containing one or more objects) that use a field name that is also in use
057 * in the outer object.  Similarly, if an array contains multiple JSON objects,
058 * then there is no restriction preventing the same field names from being
059 * used in separate objects within that array.
060 * <BR><BR>
061 * The string representation of a JSON object is an open curly brace (U+007B)
062 * followed by a comma-delimited list of the name-value pairs that comprise the
063 * fields in that object and a closing curly brace (U+007D).  Each name-value
064 * pair is represented as a JSON string followed by a colon and the appropriate
065 * string representation of the value.  There must not be a comma between the
066 * last field and the closing curly brace.  There may optionally be any amount
067 * of whitespace (where whitespace characters include the ASCII space,
068 * horizontal tab, line feed, and carriage return characters) after the open
069 * curly brace, on either or both sides of the colon separating a field name
070 * from its value, on either or both sides of commas separating fields, and
071 * before the closing curly brace.  The order in which fields appear in the
072 * string representation is not considered significant.
073 * <BR><BR>
074 * The string representation returned by the {@link #toString()} method (or
075 * appended to the buffer provided to the {@link #toString(StringBuilder)}
076 * method) will include one space before each field name and one space before
077 * the closing curly brace.  There will not be any space on either side of the
078 * colon separating the field name from its value, and there will not be any
079 * space between a field value and the comma that follows it.  The string
080 * representation of each field name will use the same logic as the
081 * {@link JSONString#toString()} method, and the string representation of each
082 * field value will be obtained using that value's {@code toString} method.
083 * <BR><BR>
084 * The normalized string representation will not include any optional spaces,
085 * and the normalized string representation of each field value will be obtained
086 * using that value's {@code toNormalizedString} method.  Field names will be
087 * treated in a case-sensitive manner, but all characters outside the LDAP
088 * printable character set will be escaped using the {@code \}{@code u}-style
089 * Unicode encoding.  The normalized string representation will have fields
090 * listed in lexicographic order.
091 */
092@NotMutable()
093@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
094public final class JSONObject
095       extends JSONValue
096{
097  /**
098   * A pre-allocated empty JSON object.
099   */
100  public static final JSONObject EMPTY_OBJECT = new JSONObject(
101       Collections.<String,JSONValue>emptyMap());
102
103
104
105  /**
106   * The serial version UID for this serializable class.
107   */
108  private static final long serialVersionUID = -4209509956709292141L;
109
110
111
112  // A counter to use in decode processing.
113  private int decodePos;
114
115  // The hash code for this JSON object.
116  private Integer hashCode;
117
118  // The set of fields for this JSON object.
119  private final Map<String,JSONValue> fields;
120
121  // The string representation for this JSON object.
122  private String stringRepresentation;
123
124  // A buffer to use in decode processing.
125  private final StringBuilder decodeBuffer;
126
127
128
129  /**
130   * Creates a new JSON object with the provided fields.
131   *
132   * @param  fields  The fields to include in this JSON object.  It may be
133   *                 {@code null} or empty if this object should not have any
134   *                 fields.
135   */
136  public JSONObject(final JSONField... fields)
137  {
138    if ((fields == null) || (fields.length == 0))
139    {
140      this.fields = Collections.emptyMap();
141    }
142    else
143    {
144      final LinkedHashMap<String,JSONValue> m =
145           new LinkedHashMap<>(fields.length);
146      for (final JSONField f : fields)
147      {
148        m.put(f.getName(), f.getValue());
149      }
150      this.fields = Collections.unmodifiableMap(m);
151    }
152
153    hashCode = null;
154    stringRepresentation = null;
155
156    // We don't need to decode anything.
157    decodePos = -1;
158    decodeBuffer = null;
159  }
160
161
162
163  /**
164   * Creates a new JSON object with the provided fields.
165   *
166   * @param  fields  The set of fields for this JSON object.  It may be
167   *                 {@code null} or empty if there should not be any fields.
168   */
169  public JSONObject(final Map<String,JSONValue> fields)
170  {
171    if (fields == null)
172    {
173      this.fields = Collections.emptyMap();
174    }
175    else
176    {
177      this.fields = Collections.unmodifiableMap(new LinkedHashMap<>(fields));
178    }
179
180    hashCode = null;
181    stringRepresentation = null;
182
183    // We don't need to decode anything.
184    decodePos = -1;
185    decodeBuffer = null;
186  }
187
188
189
190  /**
191   * Creates a new JSON object parsed from the provided string.
192   *
193   * @param  stringRepresentation  The string to parse as a JSON object.  It
194   *                               must represent exactly one JSON object.
195   *
196   * @throws  JSONException  If the provided string cannot be parsed as a valid
197   *                         JSON object.
198   */
199  public JSONObject(final String stringRepresentation)
200         throws JSONException
201  {
202    this.stringRepresentation = stringRepresentation;
203
204    final char[] chars = stringRepresentation.toCharArray();
205    decodePos = 0;
206    decodeBuffer = new StringBuilder(chars.length);
207
208    // The JSON object must start with an open curly brace.
209    final Object firstToken = readToken(chars);
210    if (! firstToken.equals('{'))
211    {
212      throw new JSONException(ERR_OBJECT_DOESNT_START_WITH_BRACE.get(
213           stringRepresentation));
214    }
215
216    final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10);
217    readObject(chars, m);
218    fields = Collections.unmodifiableMap(m);
219
220    skipWhitespace(chars);
221    if (decodePos < chars.length)
222    {
223      throw new JSONException(ERR_OBJECT_DATA_BEYOND_END.get(
224           stringRepresentation, decodePos));
225    }
226  }
227
228
229
230  /**
231   * Creates a new JSON object with the provided information.
232   *
233   * @param  fields                The set of fields for this JSON object.
234   * @param  stringRepresentation  The string representation for the JSON
235   *                               object.
236   */
237  JSONObject(final LinkedHashMap<String,JSONValue> fields,
238             final String stringRepresentation)
239  {
240    this.fields = Collections.unmodifiableMap(fields);
241    this.stringRepresentation = stringRepresentation;
242
243    hashCode = null;
244    decodePos = -1;
245    decodeBuffer = null;
246  }
247
248
249
250  /**
251   * Reads a token from the provided character array, skipping over any
252   * insignificant whitespace that may be before the token.  The token that is
253   * returned will be one of the following:
254   * <UL>
255   *   <LI>A {@code Character} that is an opening curly brace.</LI>
256   *   <LI>A {@code Character} that is a closing curly brace.</LI>
257   *   <LI>A {@code Character} that is an opening square bracket.</LI>
258   *   <LI>A {@code Character} that is a closing square bracket.</LI>
259   *   <LI>A {@code Character} that is a colon.</LI>
260   *   <LI>A {@code Character} that is a comma.</LI>
261   *   <LI>A {@link JSONBoolean}.</LI>
262   *   <LI>A {@link JSONNull}.</LI>
263   *   <LI>A {@link JSONNumber}.</LI>
264   *   <LI>A {@link JSONString}.</LI>
265   * </UL>
266   *
267   * @param  chars  The characters that comprise the string representation of
268   *                the JSON object.
269   *
270   * @return  The token that was read.
271   *
272   * @throws  JSONException  If a problem was encountered while reading the
273   *                         token.
274   */
275  private Object readToken(final char[] chars)
276          throws JSONException
277  {
278    skipWhitespace(chars);
279
280    final char c = readCharacter(chars, false);
281    switch (c)
282    {
283      case '{':
284      case '}':
285      case '[':
286      case ']':
287      case ':':
288      case ',':
289        // This is a token character that we will return as-is.
290        decodePos++;
291        return c;
292
293      case '"':
294        // This is the start of a JSON string.
295        return readString(chars);
296
297      case 't':
298      case 'f':
299        // This is the start of a JSON true or false value.
300        return readBoolean(chars);
301
302      case 'n':
303        // This is the start of a JSON null value.
304        return readNull(chars);
305
306      case '-':
307      case '0':
308      case '1':
309      case '2':
310      case '3':
311      case '4':
312      case '5':
313      case '6':
314      case '7':
315      case '8':
316      case '9':
317        // This is the start of a JSON number value.
318        return readNumber(chars);
319
320      default:
321        // This is not a valid JSON token.
322        throw new JSONException(ERR_OBJECT_INVALID_FIRST_TOKEN_CHAR.get(
323             new String(chars), String.valueOf(c), decodePos));
324
325    }
326  }
327
328
329
330  /**
331   * Skips over any valid JSON whitespace at the current position in the
332   * provided array.
333   *
334   * @param  chars  The characters that comprise the string representation of
335   *                the JSON object.
336   *
337   * @throws  JSONException  If a problem is encountered while skipping
338   *                         whitespace.
339   */
340  private void skipWhitespace(final char[] chars)
341          throws JSONException
342  {
343    while (decodePos < chars.length)
344    {
345      switch (chars[decodePos])
346      {
347        // The space, tab, newline, and carriage return characters are
348        // considered valid JSON whitespace.
349        case ' ':
350        case '\t':
351        case '\n':
352        case '\r':
353          decodePos++;
354          break;
355
356        // Technically, JSON does not provide support for comments.  But this
357        // implementation will accept three types of comments:
358        // - Comments that start with /* and end with */ (potentially spanning
359        //   multiple lines).
360        // - Comments that start with // and continue until the end of the line.
361        // - Comments that start with # and continue until the end of the line.
362        // All comments will be ignored by the parser.
363        case '/':
364          final int commentStartPos = decodePos;
365          if ((decodePos+1) >= chars.length)
366          {
367            return;
368          }
369          else if (chars[decodePos+1] == '/')
370          {
371            decodePos += 2;
372
373            // Keep reading until we encounter a newline or carriage return, or
374            // until we hit the end of the string.
375            while (decodePos < chars.length)
376            {
377              if ((chars[decodePos] == '\n') || (chars[decodePos] == '\r'))
378              {
379                break;
380              }
381              decodePos++;
382            }
383            break;
384          }
385          else if (chars[decodePos+1] == '*')
386          {
387            decodePos += 2;
388
389            // Keep reading until we encounter "*/".  We must encounter "*/"
390            // before hitting the end of the string.
391            boolean closeFound = false;
392            while (decodePos < chars.length)
393            {
394              if (chars[decodePos] == '*')
395              {
396                if (((decodePos+1) < chars.length) &&
397                    (chars[decodePos+1] == '/'))
398                {
399                  closeFound = true;
400                  decodePos += 2;
401                  break;
402                }
403              }
404              decodePos++;
405            }
406
407            if (! closeFound)
408            {
409              throw new JSONException(ERR_OBJECT_UNCLOSED_COMMENT.get(
410                   new String(chars), commentStartPos));
411            }
412            break;
413          }
414          else
415          {
416            return;
417          }
418
419        case '#':
420          // Keep reading until we encounter a newline or carriage return, or
421          // until we hit the end of the string.
422          while (decodePos < chars.length)
423          {
424            if ((chars[decodePos] == '\n') || (chars[decodePos] == '\r'))
425            {
426              break;
427            }
428            decodePos++;
429          }
430          break;
431
432        default:
433          return;
434      }
435    }
436  }
437
438
439
440  /**
441   * Reads the character at the specified position and optionally advances the
442   * position.
443   *
444   * @param  chars            The characters that comprise the string
445   *                          representation of the JSON object.
446   * @param  advancePosition  Indicates whether to advance the value of the
447   *                          position indicator after reading the character.
448   *                          If this is {@code false}, then this method will be
449   *                          used to "peek" at the next character without
450   *                          consuming it.
451   *
452   * @return  The character that was read.
453   *
454   * @throws  JSONException  If the end of the value was encountered when a
455   *                         character was expected.
456   */
457  private char readCharacter(final char[] chars, final boolean advancePosition)
458          throws JSONException
459  {
460    if (decodePos >= chars.length)
461    {
462      throw new JSONException(
463           ERR_OBJECT_UNEXPECTED_END_OF_STRING.get(new String(chars)));
464    }
465
466    final char c = chars[decodePos];
467    if (advancePosition)
468    {
469      decodePos++;
470    }
471    return c;
472  }
473
474
475
476  /**
477   * Reads a JSON string staring at the specified position in the provided
478   * character array.
479   *
480   * @param  chars  The characters that comprise the string representation of
481   *                the JSON object.
482   *
483   * @return  The JSON string that was read.
484   *
485   * @throws  JSONException  If a problem was encountered while reading the JSON
486   *                         string.
487   */
488  private JSONString readString(final char[] chars)
489          throws JSONException
490  {
491    // Create a buffer to hold the string.  Note that if we've gotten here then
492    // we already know that the character at the provided position is a quote,
493    // so we can read past it in the process.
494    final int startPos = decodePos++;
495    decodeBuffer.setLength(0);
496    while (true)
497    {
498      final char c = readCharacter(chars, true);
499      if (c == '\\')
500      {
501        final int escapedCharPos = decodePos;
502        final char escapedChar = readCharacter(chars, true);
503        switch (escapedChar)
504        {
505          case '"':
506          case '\\':
507          case '/':
508            decodeBuffer.append(escapedChar);
509            break;
510          case 'b':
511            decodeBuffer.append('\b');
512            break;
513          case 'f':
514            decodeBuffer.append('\f');
515            break;
516          case 'n':
517            decodeBuffer.append('\n');
518            break;
519          case 'r':
520            decodeBuffer.append('\r');
521            break;
522          case 't':
523            decodeBuffer.append('\t');
524            break;
525
526          case 'u':
527            final char[] hexChars =
528            {
529              readCharacter(chars, true),
530              readCharacter(chars, true),
531              readCharacter(chars, true),
532              readCharacter(chars, true)
533            };
534            try
535            {
536              decodeBuffer.append(
537                   (char) Integer.parseInt(new String(hexChars), 16));
538            }
539            catch (final Exception e)
540            {
541              Debug.debugException(e);
542              throw new JSONException(
543                   ERR_OBJECT_INVALID_UNICODE_ESCAPE.get(new String(chars),
544                        escapedCharPos),
545                   e);
546            }
547            break;
548
549          default:
550            throw new JSONException(ERR_OBJECT_INVALID_ESCAPED_CHAR.get(
551                 new String(chars), escapedChar, escapedCharPos));
552        }
553      }
554      else if (c == '"')
555      {
556        return new JSONString(decodeBuffer.toString(),
557             new String(chars, startPos, (decodePos - startPos)));
558      }
559      else
560      {
561        if (c <= '\u001F')
562        {
563          throw new JSONException(ERR_OBJECT_UNESCAPED_CONTROL_CHAR.get(
564               new String(chars), String.format("%04X", (int) c),
565               (decodePos - 1)));
566        }
567
568        decodeBuffer.append(c);
569      }
570    }
571  }
572
573
574
575  /**
576   * Reads a JSON Boolean staring at the specified position in the provided
577   * character array.
578   *
579   * @param  chars  The characters that comprise the string representation of
580   *                the JSON object.
581   *
582   * @return  The JSON Boolean that was read.
583   *
584   * @throws  JSONException  If a problem was encountered while reading the JSON
585   *                         Boolean.
586   */
587  private JSONBoolean readBoolean(final char[] chars)
588          throws JSONException
589  {
590    final int startPos = decodePos;
591    final char firstCharacter = readCharacter(chars, true);
592    if (firstCharacter == 't')
593    {
594      if ((readCharacter(chars, true) == 'r') &&
595          (readCharacter(chars, true) == 'u') &&
596          (readCharacter(chars, true) == 'e'))
597      {
598        return JSONBoolean.TRUE;
599      }
600    }
601    else if (firstCharacter == 'f')
602    {
603      if ((readCharacter(chars, true) == 'a') &&
604          (readCharacter(chars, true) == 'l') &&
605          (readCharacter(chars, true) == 's') &&
606          (readCharacter(chars, true) == 'e'))
607      {
608        return JSONBoolean.FALSE;
609      }
610    }
611
612    throw new JSONException(ERR_OBJECT_UNABLE_TO_PARSE_BOOLEAN.get(
613         new String(chars), startPos));
614  }
615
616
617
618  /**
619   * Reads a JSON null staring at the specified position in the provided
620   * character array.
621   *
622   * @param  chars  The characters that comprise the string representation of
623   *                the JSON object.
624   *
625   * @return  The JSON null that was read.
626   *
627   * @throws  JSONException  If a problem was encountered while reading the JSON
628   *                         null.
629   */
630  private JSONNull readNull(final char[] chars)
631          throws JSONException
632  {
633    final int startPos = decodePos;
634    if ((readCharacter(chars, true) == 'n') &&
635        (readCharacter(chars, true) == 'u') &&
636        (readCharacter(chars, true) == 'l') &&
637        (readCharacter(chars, true) == 'l'))
638    {
639      return JSONNull.NULL;
640    }
641
642    throw new JSONException(ERR_OBJECT_UNABLE_TO_PARSE_NULL.get(
643         new String(chars), startPos));
644  }
645
646
647
648  /**
649   * Reads a JSON number staring at the specified position in the provided
650   * character array.
651   *
652   * @param  chars  The characters that comprise the string representation of
653   *                the JSON object.
654   *
655   * @return  The JSON number that was read.
656   *
657   * @throws  JSONException  If a problem was encountered while reading the JSON
658   *                         number.
659   */
660  private JSONNumber readNumber(final char[] chars)
661          throws JSONException
662  {
663    // Read until we encounter whitespace, a comma, a closing square bracket, or
664    // a closing curly brace.  Then try to parse what we read as a number.
665    final int startPos = decodePos;
666    decodeBuffer.setLength(0);
667
668    while (true)
669    {
670      final char c = readCharacter(chars, true);
671      switch (c)
672      {
673        case ' ':
674        case '\t':
675        case '\n':
676        case '\r':
677        case ',':
678        case ']':
679        case '}':
680          // We need to decrement the position indicator since the last one we
681          // read wasn't part of the number.
682          decodePos--;
683          return new JSONNumber(decodeBuffer.toString());
684
685        default:
686          decodeBuffer.append(c);
687      }
688    }
689  }
690
691
692
693  /**
694   * Reads a JSON array starting at the specified position in the provided
695   * character array.  Note that this method assumes that the opening square
696   * bracket has already been read.
697   *
698   * @param  chars  The characters that comprise the string representation of
699   *                the JSON object.
700   *
701   * @return  The JSON array that was read.
702   *
703   * @throws  JSONException  If a problem was encountered while reading the JSON
704   *                         array.
705   */
706  private JSONArray readArray(final char[] chars)
707          throws JSONException
708  {
709    // The opening square bracket will have already been consumed, so read
710    // JSON values until we hit a closing square bracket.
711    final ArrayList<JSONValue> values = new ArrayList<>(10);
712    boolean firstToken = true;
713    while (true)
714    {
715      // If this is the first time through, it is acceptable to find a closing
716      // square bracket.  Otherwise, we expect to find a JSON value, an opening
717      // square bracket to denote the start of an embedded array, or an opening
718      // curly brace to denote the start of an embedded JSON object.
719      int p = decodePos;
720      Object token = readToken(chars);
721      if (token instanceof JSONValue)
722      {
723        values.add((JSONValue) token);
724      }
725      else if (token.equals('['))
726      {
727        values.add(readArray(chars));
728      }
729      else if (token.equals('{'))
730      {
731        final LinkedHashMap<String,JSONValue> fieldMap =
732             new LinkedHashMap<>(10);
733        values.add(readObject(chars, fieldMap));
734      }
735      else if (token.equals(']') && firstToken)
736      {
737        // It's an empty array.
738        return JSONArray.EMPTY_ARRAY;
739      }
740      else
741      {
742        throw new JSONException(
743             ERR_OBJECT_INVALID_TOKEN_WHEN_ARRAY_VALUE_EXPECTED.get(
744                  new String(chars), String.valueOf(token), p));
745      }
746
747      firstToken = false;
748
749
750      // If we've gotten here, then we found a JSON value.  It must be followed
751      // by either a comma (to indicate that there's at least one more value) or
752      // a closing square bracket (to denote the end of the array).
753      p = decodePos;
754      token = readToken(chars);
755      if (token.equals(']'))
756      {
757        return new JSONArray(values);
758      }
759      else if (! token.equals(','))
760      {
761        throw new JSONException(
762             ERR_OBJECT_INVALID_TOKEN_WHEN_ARRAY_COMMA_OR_BRACKET_EXPECTED.get(
763                  new String(chars), String.valueOf(token), p));
764      }
765    }
766  }
767
768
769
770  /**
771   * Reads a JSON object starting at the specified position in the provided
772   * character array.  Note that this method assumes that the opening curly
773   * brace has already been read.
774   *
775   * @param  chars   The characters that comprise the string representation of
776   *                 the JSON object.
777   * @param  fields  The map into which to place the fields that are read.  The
778   *                 returned object will include an unmodifiable view of this
779   *                 map, but the caller may use the map directly if desired.
780   *
781   * @return  The JSON object that was read.
782   *
783   * @throws  JSONException  If a problem was encountered while reading the JSON
784   *                         object.
785   */
786  private JSONObject readObject(final char[] chars,
787                                final Map<String,JSONValue> fields)
788          throws JSONException
789  {
790    boolean firstField = true;
791    while (true)
792    {
793      // Read the next token.  It must be a JSONString, unless we haven't read
794      // any fields yet in which case it can be a closing curly brace to
795      // indicate that it's an empty object.
796      int p = decodePos;
797      final String fieldName;
798      Object token = readToken(chars);
799      if (token instanceof JSONString)
800      {
801        fieldName = ((JSONString) token).stringValue();
802        if (fields.containsKey(fieldName))
803        {
804          throw new JSONException(ERR_OBJECT_DUPLICATE_FIELD.get(
805               new String(chars), fieldName));
806        }
807      }
808      else if (firstField && token.equals('}'))
809      {
810        return new JSONObject(fields);
811      }
812      else
813      {
814        throw new JSONException(ERR_OBJECT_EXPECTED_STRING.get(
815             new String(chars), String.valueOf(token), p));
816      }
817      firstField = false;
818
819      // Read the next token.  It must be a colon.
820      p = decodePos;
821      token = readToken(chars);
822      if (! token.equals(':'))
823      {
824        throw new JSONException(ERR_OBJECT_EXPECTED_COLON.get(new String(chars),
825             String.valueOf(token), p));
826      }
827
828      // Read the next token.  It must be one of the following:
829      // - A JSONValue
830      // - An opening square bracket, designating the start of an array.
831      // - An opening curly brace, designating the start of an object.
832      p = decodePos;
833      token = readToken(chars);
834      if (token instanceof JSONValue)
835      {
836        fields.put(fieldName, (JSONValue) token);
837      }
838      else if (token.equals('['))
839      {
840        final JSONArray a = readArray(chars);
841        fields.put(fieldName, a);
842      }
843      else if (token.equals('{'))
844      {
845        final LinkedHashMap<String,JSONValue> m = new LinkedHashMap<>(10);
846        final JSONObject o = readObject(chars, m);
847        fields.put(fieldName, o);
848      }
849      else
850      {
851        throw new JSONException(ERR_OBJECT_EXPECTED_VALUE.get(new String(chars),
852             String.valueOf(token), p, fieldName));
853      }
854
855      // Read the next token.  It must be either a comma (to indicate that
856      // there will be another field) or a closing curly brace (to indicate
857      // that the end of the object has been reached).
858      p = decodePos;
859      token = readToken(chars);
860      if (token.equals('}'))
861      {
862        return new JSONObject(fields);
863      }
864      else if (! token.equals(','))
865      {
866        throw new JSONException(ERR_OBJECT_EXPECTED_COMMA_OR_CLOSE_BRACE.get(
867             new String(chars), String.valueOf(token), p));
868      }
869    }
870  }
871
872
873
874  /**
875   * Retrieves a map of the fields contained in this JSON object.
876   *
877   * @return  A map of the fields contained in this JSON object.
878   */
879  public Map<String,JSONValue> getFields()
880  {
881    return fields;
882  }
883
884
885
886  /**
887   * Retrieves the value for the specified field.
888   *
889   * @param  name  The name of the field for which to retrieve the value.  It
890   *               will be treated in a case-sensitive manner.
891   *
892   * @return  The value for the specified field, or {@code null} if the
893   *          requested field is not present in the JSON object.
894   */
895  public JSONValue getField(final String name)
896  {
897    return fields.get(name);
898  }
899
900
901
902  /**
903   * Retrieves the value of the specified field as a string.
904   *
905   * @param  name  The name of the field for which to retrieve the string value.
906   *               It will be treated in a case-sensitive manner.
907   *
908   * @return  The value of the specified field as a string, or {@code null} if
909   *          this JSON object does not have a field with the specified name, or
910   *          if the value of that field is not a string.
911   */
912  public String getFieldAsString(final String name)
913  {
914    final JSONValue value = fields.get(name);
915    if ((value == null) || (! (value instanceof JSONString)))
916    {
917      return null;
918    }
919
920    return ((JSONString) value).stringValue();
921  }
922
923
924
925  /**
926   * Retrieves the value of the specified field as a Boolean.
927   *
928   * @param  name  The name of the field for which to retrieve the Boolean
929   *               value.  It will be treated in a case-sensitive manner.
930   *
931   * @return  The value of the specified field as a Boolean, or {@code null} if
932   *          this JSON object does not have a field with the specified name, or
933   *          if the value of that field is not a Boolean.
934   */
935  public Boolean getFieldAsBoolean(final String name)
936  {
937    final JSONValue value = fields.get(name);
938    if ((value == null) || (! (value instanceof JSONBoolean)))
939    {
940      return null;
941    }
942
943    return ((JSONBoolean) value).booleanValue();
944  }
945
946
947
948  /**
949   * Retrieves the value of the specified field as an integer.
950   *
951   * @param  name  The name of the field for which to retrieve the integer
952   *               value.  It will be treated in a case-sensitive manner.
953   *
954   * @return  The value of the specified field as an integer, or {@code null} if
955   *          this JSON object does not have a field with the specified name, or
956   *          if the value of that field is not a number that can be exactly
957   *          represented as an integer.
958   */
959  public Integer getFieldAsInteger(final String name)
960  {
961    final JSONValue value = fields.get(name);
962    if ((value == null) || (! (value instanceof JSONNumber)))
963    {
964      return null;
965    }
966
967    try
968    {
969      final JSONNumber number = (JSONNumber) value;
970      return number.getValue().intValueExact();
971    }
972    catch (final Exception e)
973    {
974      Debug.debugException(e);
975      return null;
976    }
977  }
978
979
980
981  /**
982   * Retrieves the value of the specified field as a long.
983   *
984   * @param  name  The name of the field for which to retrieve the long value.
985   *               It will be treated in a case-sensitive manner.
986   *
987   * @return  The value of the specified field as a long, or {@code null} if
988   *          this JSON object does not have a field with the specified name, or
989   *          if the value of that field is not a number that can be exactly
990   *          represented as a long.
991   */
992  public Long getFieldAsLong(final String name)
993  {
994    final JSONValue value = fields.get(name);
995    if ((value == null) || (! (value instanceof JSONNumber)))
996    {
997      return null;
998    }
999
1000    try
1001    {
1002      final JSONNumber number = (JSONNumber) value;
1003      return number.getValue().longValueExact();
1004    }
1005    catch (final Exception e)
1006    {
1007      Debug.debugException(e);
1008      return null;
1009    }
1010  }
1011
1012
1013
1014  /**
1015   * Retrieves the value of the specified field as a BigDecimal.
1016   *
1017   * @param  name  The name of the field for which to retrieve the BigDecimal
1018   *               value.  It will be treated in a case-sensitive manner.
1019   *
1020   * @return  The value of the specified field as a BigDecimal, or {@code null}
1021   *          if this JSON object does not have a field with the specified name,
1022   *          or if the value of that field is not a number.
1023   */
1024  public BigDecimal getFieldAsBigDecimal(final String name)
1025  {
1026    final JSONValue value = fields.get(name);
1027    if ((value == null) || (! (value instanceof JSONNumber)))
1028    {
1029      return null;
1030    }
1031
1032    return ((JSONNumber) value).getValue();
1033  }
1034
1035
1036
1037  /**
1038   * Retrieves the value of the specified field as a JSON object.
1039   *
1040   * @param  name  The name of the field for which to retrieve the value.  It
1041   *               will be treated in a case-sensitive manner.
1042   *
1043   * @return  The value of the specified field as a JSON object, or {@code null}
1044   *          if this JSON object does not have a field with the specified name,
1045   *          or if the value of that field is not an object.
1046   */
1047  public JSONObject getFieldAsObject(final String name)
1048  {
1049    final JSONValue value = fields.get(name);
1050    if ((value == null) || (! (value instanceof JSONObject)))
1051    {
1052      return null;
1053    }
1054
1055    return (JSONObject) value;
1056  }
1057
1058
1059
1060  /**
1061   * Retrieves a list of the elements in the specified array field.
1062   *
1063   * @param  name  The name of the field for which to retrieve the array values.
1064   *               It will be treated in a case-sensitive manner.
1065   *
1066   * @return  A list of the elements in the specified array field, or
1067   *          {@code null} if this JSON object does not have a field with the
1068   *          specified name, or if the value of that field is not an array.
1069   */
1070  public List<JSONValue> getFieldAsArray(final String name)
1071  {
1072    final JSONValue value = fields.get(name);
1073    if ((value == null) || (! (value instanceof JSONArray)))
1074    {
1075      return null;
1076    }
1077
1078    return ((JSONArray) value).getValues();
1079  }
1080
1081
1082
1083  /**
1084   * Indicates whether this JSON object has a null field with the specified
1085   * name.
1086   *
1087   * @param  name  The name of the field for which to make the determination.
1088   *               It will be treated in a case-sensitive manner.
1089   *
1090   * @return  {@code true} if this JSON object has a null field with the
1091   *          specified name, or {@code false} if this JSON object does not have
1092   *          a field with the specified name, or if the value of that field is
1093   *          not a null.
1094   */
1095  public boolean hasNullField(final String name)
1096  {
1097    final JSONValue value = fields.get(name);
1098    return ((value != null) && (value instanceof JSONNull));
1099  }
1100
1101
1102
1103  /**
1104   * {@inheritDoc}
1105   */
1106  @Override()
1107  public int hashCode()
1108  {
1109    if (hashCode == null)
1110    {
1111      int hc = 0;
1112      for (final Map.Entry<String,JSONValue> e : fields.entrySet())
1113      {
1114        hc += e.getKey().hashCode() + e.getValue().hashCode();
1115      }
1116
1117      hashCode = hc;
1118    }
1119
1120    return hashCode;
1121  }
1122
1123
1124
1125  /**
1126   * {@inheritDoc}
1127   */
1128  @Override()
1129  public boolean equals(final Object o)
1130  {
1131    if (o == this)
1132    {
1133      return true;
1134    }
1135
1136    if (o instanceof JSONObject)
1137    {
1138      final JSONObject obj = (JSONObject) o;
1139      return fields.equals(obj.fields);
1140    }
1141
1142    return false;
1143  }
1144
1145
1146
1147  /**
1148   * Indicates whether this JSON object is considered equal to the provided
1149   * object, subject to the specified constraints.
1150   *
1151   * @param  o                    The object to compare against this JSON
1152   *                              object.  It must not be {@code null}.
1153   * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
1154   *                              capitalization in field names.
1155   * @param  ignoreValueCase      Indicates whether to ignore differences in
1156   *                              capitalization in values that are JSON
1157   *                              strings.
1158   * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
1159   *                              order of elements within an array.
1160   *
1161   * @return  {@code true} if this JSON object is considered equal to the
1162   *          provided object (subject to the specified constraints), or
1163   *          {@code false} if not.
1164   */
1165  public boolean equals(final JSONObject o, final boolean ignoreFieldNameCase,
1166                        final boolean ignoreValueCase,
1167                        final boolean ignoreArrayOrder)
1168  {
1169    // See if we can do a straight-up Map.equals.  If so, just do that.
1170    if ((! ignoreFieldNameCase) && (! ignoreValueCase) && (! ignoreArrayOrder))
1171    {
1172      return fields.equals(o.fields);
1173    }
1174
1175    // Make sure they have the same number of fields.
1176    if (fields.size() != o.fields.size())
1177    {
1178      return false;
1179    }
1180
1181    // Optimize for the case in which we field names are case sensitive.
1182    if (! ignoreFieldNameCase)
1183    {
1184      for (final Map.Entry<String,JSONValue> e : fields.entrySet())
1185      {
1186        final JSONValue thisValue = e.getValue();
1187        final JSONValue thatValue = o.fields.get(e.getKey());
1188        if (thatValue == null)
1189        {
1190          return false;
1191        }
1192
1193        if (! thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
1194             ignoreArrayOrder))
1195        {
1196          return false;
1197        }
1198      }
1199
1200      return true;
1201    }
1202
1203
1204    // If we've gotten here, then we know that we need to treat field names in
1205    // a case-insensitive manner.  Create a new map that we can remove fields
1206    // from as we find matches.  This can help avoid false-positive matches in
1207    // which multiple fields in the first map match the same field in the second
1208    // map (e.g., because they have field names that differ only in case and
1209    // values that are logically equivalent).  It also makes iterating through
1210    // the values faster as we make more progress.
1211    final HashMap<String,JSONValue> thatMap = new HashMap<>(o.fields);
1212    final Iterator<Map.Entry<String,JSONValue>> thisIterator =
1213         fields.entrySet().iterator();
1214    while (thisIterator.hasNext())
1215    {
1216      final Map.Entry<String,JSONValue> thisEntry = thisIterator.next();
1217      final String thisFieldName = thisEntry.getKey();
1218      final JSONValue thisValue = thisEntry.getValue();
1219
1220      final Iterator<Map.Entry<String,JSONValue>> thatIterator =
1221           thatMap.entrySet().iterator();
1222
1223      boolean found = false;
1224      while (thatIterator.hasNext())
1225      {
1226        final Map.Entry<String,JSONValue> thatEntry = thatIterator.next();
1227        final String thatFieldName = thatEntry.getKey();
1228        if (! thisFieldName.equalsIgnoreCase(thatFieldName))
1229        {
1230          continue;
1231        }
1232
1233        final JSONValue thatValue = thatEntry.getValue();
1234        if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
1235             ignoreArrayOrder))
1236        {
1237          found = true;
1238          thatIterator.remove();
1239          break;
1240        }
1241      }
1242
1243      if (! found)
1244      {
1245        return false;
1246      }
1247    }
1248
1249    return true;
1250  }
1251
1252
1253
1254  /**
1255   * {@inheritDoc}
1256   */
1257  @Override()
1258  public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
1259                        final boolean ignoreValueCase,
1260                        final boolean ignoreArrayOrder)
1261  {
1262    return ((v instanceof JSONObject) &&
1263         equals((JSONObject) v, ignoreFieldNameCase, ignoreValueCase,
1264              ignoreArrayOrder));
1265  }
1266
1267
1268
1269  /**
1270   * Retrieves a string representation of this JSON object.  If this object was
1271   * decoded from a string, then the original string representation will be
1272   * used.  Otherwise, a single-line string representation will be constructed.
1273   *
1274   * @return  A string representation of this JSON object.
1275   */
1276  @Override()
1277  public String toString()
1278  {
1279    if (stringRepresentation == null)
1280    {
1281      final StringBuilder buffer = new StringBuilder();
1282      toString(buffer);
1283      stringRepresentation = buffer.toString();
1284    }
1285
1286    return stringRepresentation;
1287  }
1288
1289
1290
1291  /**
1292   * Appends a string representation of this JSON object to the provided buffer.
1293   * If this object was decoded from a string, then the original string
1294   * representation will be used.  Otherwise, a single-line string
1295   * representation will be constructed.
1296   *
1297   * @param  buffer  The buffer to which the information should be appended.
1298   */
1299  @Override()
1300  public void toString(final StringBuilder buffer)
1301  {
1302    if (stringRepresentation != null)
1303    {
1304      buffer.append(stringRepresentation);
1305      return;
1306    }
1307
1308    buffer.append("{ ");
1309
1310    final Iterator<Map.Entry<String,JSONValue>> iterator =
1311         fields.entrySet().iterator();
1312    while (iterator.hasNext())
1313    {
1314      final Map.Entry<String,JSONValue> e = iterator.next();
1315      JSONString.encodeString(e.getKey(), buffer);
1316      buffer.append(':');
1317      e.getValue().toString(buffer);
1318
1319      if (iterator.hasNext())
1320      {
1321        buffer.append(',');
1322      }
1323      buffer.append(' ');
1324    }
1325
1326    buffer.append('}');
1327  }
1328
1329
1330
1331  /**
1332   * Retrieves a user-friendly string representation of this JSON object that
1333   * may be formatted across multiple lines for better readability.  The last
1334   * line will not include a trailing line break.
1335   *
1336   * @return  A user-friendly string representation of this JSON object that may
1337   *          be formatted across multiple lines for better readability.
1338   */
1339  public String toMultiLineString()
1340  {
1341    final JSONBuffer jsonBuffer = new JSONBuffer(null, 0, true);
1342    appendToJSONBuffer(jsonBuffer);
1343    return jsonBuffer.toString();
1344  }
1345
1346
1347
1348  /**
1349   * Retrieves a single-line string representation of this JSON object.
1350   *
1351   * @return  A single-line string representation of this JSON object.
1352   */
1353  @Override()
1354  public String toSingleLineString()
1355  {
1356    final StringBuilder buffer = new StringBuilder();
1357    toSingleLineString(buffer);
1358    return buffer.toString();
1359  }
1360
1361
1362
1363  /**
1364   * Appends a single-line string representation of this JSON object to the
1365   * provided buffer.
1366   *
1367   * @param  buffer  The buffer to which the information should be appended.
1368   */
1369  @Override()
1370  public void toSingleLineString(final StringBuilder buffer)
1371  {
1372    buffer.append("{ ");
1373
1374    final Iterator<Map.Entry<String,JSONValue>> iterator =
1375         fields.entrySet().iterator();
1376    while (iterator.hasNext())
1377    {
1378      final Map.Entry<String,JSONValue> e = iterator.next();
1379      JSONString.encodeString(e.getKey(), buffer);
1380      buffer.append(':');
1381      e.getValue().toSingleLineString(buffer);
1382
1383      if (iterator.hasNext())
1384      {
1385        buffer.append(',');
1386      }
1387      buffer.append(' ');
1388    }
1389
1390    buffer.append('}');
1391  }
1392
1393
1394
1395  /**
1396   * Retrieves a normalized string representation of this JSON object.  The
1397   * normalized representation of the JSON object will have the following
1398   * characteristics:
1399   * <UL>
1400   *   <LI>It will not include any line breaks.</LI>
1401   *   <LI>It will not include any spaces around the enclosing braces.</LI>
1402   *   <LI>It will not include any spaces around the commas used to separate
1403   *       fields.</LI>
1404   *   <LI>Field names will be treated in a case-sensitive manner and will not
1405   *       be altered.</LI>
1406   *   <LI>Field values will be normalized.</LI>
1407   *   <LI>Fields will be listed in lexicographic order by field name.</LI>
1408   * </UL>
1409   *
1410   * @return  A normalized string representation of this JSON object.
1411   */
1412  @Override()
1413  public String toNormalizedString()
1414  {
1415    final StringBuilder buffer = new StringBuilder();
1416    toNormalizedString(buffer);
1417    return buffer.toString();
1418  }
1419
1420
1421
1422  /**
1423   * Appends a normalized string representation of this JSON object to the
1424   * provided buffer.  The normalized representation of the JSON object will
1425   * have the following characteristics:
1426   * <UL>
1427   *   <LI>It will not include any line breaks.</LI>
1428   *   <LI>It will not include any spaces around the enclosing braces.</LI>
1429   *   <LI>It will not include any spaces around the commas used to separate
1430   *       fields.</LI>
1431   *   <LI>Field names will be treated in a case-sensitive manner and will not
1432   *       be altered.</LI>
1433   *   <LI>Field values will be normalized.</LI>
1434   *   <LI>Fields will be listed in lexicographic order by field name.</LI>
1435   * </UL>
1436   *
1437   * @param  buffer  The buffer to which the information should be appended.
1438   */
1439  @Override()
1440  public void toNormalizedString(final StringBuilder buffer)
1441  {
1442    // The normalized representation needs to have the fields in a predictable
1443    // order, which we will accomplish using the lexicographic ordering that a
1444    // TreeMap will provide.  Field names will be case sensitive, but we still
1445    // need to construct a normalized way of escaping non-printable characters
1446    // in each field.
1447    final StringBuilder tempBuffer;
1448    if (decodeBuffer == null)
1449    {
1450      tempBuffer = new StringBuilder(20);
1451    }
1452    else
1453    {
1454      tempBuffer = decodeBuffer;
1455    }
1456
1457    final TreeMap<String,String> m = new TreeMap<>();
1458    for (final Map.Entry<String,JSONValue> e : fields.entrySet())
1459    {
1460      tempBuffer.setLength(0);
1461      tempBuffer.append('"');
1462      for (final char c : e.getKey().toCharArray())
1463      {
1464        if (StaticUtils.isPrintable(c))
1465        {
1466          tempBuffer.append(c);
1467        }
1468        else
1469        {
1470          tempBuffer.append("\\u");
1471          tempBuffer.append(String.format("%04X", (int) c));
1472        }
1473      }
1474      tempBuffer.append('"');
1475      final String normalizedKey = tempBuffer.toString();
1476
1477      tempBuffer.setLength(0);
1478      e.getValue().toNormalizedString(tempBuffer);
1479      m.put(normalizedKey, tempBuffer.toString());
1480    }
1481
1482    buffer.append('{');
1483    final Iterator<Map.Entry<String,String>> iterator = m.entrySet().iterator();
1484    while (iterator.hasNext())
1485    {
1486      final Map.Entry<String,String> e = iterator.next();
1487      buffer.append(e.getKey());
1488      buffer.append(':');
1489      buffer.append(e.getValue());
1490
1491      if (iterator.hasNext())
1492      {
1493        buffer.append(',');
1494      }
1495    }
1496
1497    buffer.append('}');
1498  }
1499
1500
1501
1502  /**
1503   * {@inheritDoc}
1504   */
1505  @Override()
1506  public void appendToJSONBuffer(final JSONBuffer buffer)
1507  {
1508    buffer.beginObject();
1509
1510    for (final Map.Entry<String,JSONValue> field : fields.entrySet())
1511    {
1512      final String name = field.getKey();
1513      final JSONValue value = field.getValue();
1514      value.appendToJSONBuffer(name, buffer);
1515    }
1516
1517    buffer.endObject();
1518  }
1519
1520
1521
1522  /**
1523   * {@inheritDoc}
1524   */
1525  @Override()
1526  public void appendToJSONBuffer(final String fieldName,
1527                                 final JSONBuffer buffer)
1528  {
1529    buffer.beginObject(fieldName);
1530
1531    for (final Map.Entry<String,JSONValue> field : fields.entrySet())
1532    {
1533      final String name = field.getKey();
1534      final JSONValue value = field.getValue();
1535      value.appendToJSONBuffer(name, buffer);
1536    }
1537
1538    buffer.endObject();
1539  }
1540}