001    /*
002     * Copyright 2015-2016 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015-2016 UnboundID Corp.
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     */
021    package com.unboundid.util.json;
022    
023    
024    
025    import java.util.ArrayList;
026    import java.util.Collections;
027    import java.util.HashMap;
028    import java.util.Iterator;
029    import java.util.LinkedHashMap;
030    import java.util.Map;
031    import java.util.TreeMap;
032    
033    import com.unboundid.util.Debug;
034    import com.unboundid.util.NotMutable;
035    import com.unboundid.util.StaticUtils;
036    import com.unboundid.util.ThreadSafety;
037    import com.unboundid.util.ThreadSafetyLevel;
038    
039    import static com.unboundid.util.json.JSONMessages.*;
040    
041    
042    
043    /**
044     * This class provides an implementation of a JSON value that represents an
045     * object with zero or more name-value pairs.  In each pair, the name is a JSON
046     * string and the value is any type of JSON value ({@code null}, {@code true},
047     * {@code false}, number, string, array, or object).  Although the ECMA-404
048     * specification does not explicitly forbid a JSON object from having multiple
049     * fields with the same name, RFC 7159 section 4 states that field names should
050     * be unique, and this implementation does not support objects in which multiple
051     * fields have the same name.  Note that this uniqueness constraint only applies
052     * to the fields directly contained within an object, and does not prevent an
053     * object from having a field value that is an object (or that is an array
054     * containing one or more objects) that use a field name that is also in use
055     * in the outer object.  Similarly, if an array contains multiple JSON objects,
056     * then there is no restriction preventing the same field names from being
057     * used in separate objects within that array.
058     * <BR><BR>
059     * The string representation of a JSON object is an open curly brace (U+007B)
060     * followed by a comma-delimited list of the name-value pairs that comprise the
061     * fields in that object and a closing curly brace (U+007D).  Each name-value
062     * pair is represented as a JSON string followed by a colon and the appropriate
063     * string representation of the value.  There must not be a comma between the
064     * last field and the closing curly brace.  There may optionally be any amount
065     * of whitespace (where whitespace characters include the ASCII space,
066     * horizontal tab, line feed, and carriage return characters) after the open
067     * curly brace, on either or both sides of the colon separating a field name
068     * from its value, on either or both sides of commas separating fields, and
069     * before the closing curly brace.  The order in which fields appear in the
070     * string representation is not considered significant.
071     * <BR><BR>
072     * The string representation returned by the {@link #toString()} method (or
073     * appended to the buffer provided to the {@link #toString(StringBuilder)}
074     * method) will include one space before each field name and one space before
075     * the closing curly brace.  There will not be any space on either side of the
076     * colon separating the field name from its value, and there will not be any
077     * space between a field value and the comma that follows it.  The string
078     * representation of each field name will use the same logic as the
079     * {@link JSONString#toString()} method, and the string representation of each
080     * field value will be obtained using that value's {@code toString} method.
081     * <BR><BR>
082     * The normalized string representation will not include any optional spaces,
083     * and the normalized string representation of each field value will be obtained
084     * using that value's {@code toNormalizedString} method.  Field names will be
085     * treated in a case-sensitive manner, but all characters outside the LDAP
086     * printable character set will be escaped using the {@code \}{@code u}-style
087     * Unicode encoding.  The normalized string representation will have fields
088     * listed in lexicographic order.
089     */
090    @NotMutable()
091    @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
092    public final class JSONObject
093           extends JSONValue
094    {
095      /**
096       * A pre-allocated empty JSON object.
097       */
098      public static final JSONObject EMPTY_OBJECT = new JSONObject(
099           Collections.<String,JSONValue>emptyMap());
100    
101    
102    
103      /**
104       * The serial version UID for this serializable class.
105       */
106      private static final long serialVersionUID = -4209509956709292141L;
107    
108    
109    
110      // A counter to use in decode processing.
111      private int decodePos;
112    
113      // The hash code for this JSON object.
114      private Integer hashCode;
115    
116      // The set of fields for this JSON object.
117      private final Map<String,JSONValue> fields;
118    
119      // The string representation for this JSON object.
120      private String stringRepresentation;
121    
122      // A buffer to use in decode processing.
123      private final StringBuilder decodeBuffer;
124    
125    
126    
127      /**
128       * Creates a new JSON object with the provided fields.
129       *
130       * @param  fields  The fields to include in this JSON object.  It may be
131       *                 {@code null} or empty if this object should not have any
132       *                 fields.
133       */
134      public JSONObject(final JSONField... fields)
135      {
136        if ((fields == null) || (fields.length == 0))
137        {
138          this.fields = Collections.emptyMap();
139        }
140        else
141        {
142          final LinkedHashMap<String,JSONValue> m =
143               new LinkedHashMap<String,JSONValue>(fields.length);
144          for (final JSONField f : fields)
145          {
146            m.put(f.getName(), f.getValue());
147          }
148          this.fields = Collections.unmodifiableMap(m);
149        }
150    
151        hashCode = null;
152        stringRepresentation = null;
153    
154        // We don't need to decode anything.
155        decodePos = -1;
156        decodeBuffer = null;
157      }
158    
159    
160    
161      /**
162       * Creates a new JSON object with the provided fields.
163       *
164       * @param  fields  The set of fields for this JSON object.  It may be
165       *                 {@code null} or empty if there should not be any fields.
166       */
167      public JSONObject(final Map<String,JSONValue> fields)
168      {
169        if (fields == null)
170        {
171          this.fields = Collections.emptyMap();
172        }
173        else
174        {
175          this.fields = Collections.unmodifiableMap(
176               new LinkedHashMap<String,JSONValue>(fields));
177        }
178    
179        hashCode = null;
180        stringRepresentation = null;
181    
182        // We don't need to decode anything.
183        decodePos = -1;
184        decodeBuffer = null;
185      }
186    
187    
188    
189      /**
190       * Creates a new JSON object parsed from the provided string.
191       *
192       * @param  stringRepresentation  The string to parse as a JSON object.  It
193       *                               must represent exactly one JSON object.
194       *
195       * @throws  JSONException  If the provided string cannot be parsed as a valid
196       *                         JSON object.
197       */
198      public JSONObject(final String stringRepresentation)
199             throws JSONException
200      {
201        this.stringRepresentation = stringRepresentation;
202    
203        final char[] chars = stringRepresentation.toCharArray();
204        decodePos = 0;
205        decodeBuffer = new StringBuilder(chars.length);
206    
207        // The JSON object must start with an open curly brace.
208        final Object firstToken = readToken(chars);
209        if (! firstToken.equals('{'))
210        {
211          throw new JSONException(ERR_OBJECT_DOESNT_START_WITH_BRACE.get(
212               stringRepresentation));
213        }
214    
215        final LinkedHashMap<String,JSONValue> m =
216             new LinkedHashMap<String,JSONValue>(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<JSONValue>(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<String,JSONValue>(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 =
846                 new LinkedHashMap<String,JSONValue>(10);
847            final JSONObject o = readObject(chars, m);
848            fields.put(fieldName, o);
849          }
850          else
851          {
852            throw new JSONException(ERR_OBJECT_EXPECTED_VALUE.get(new String(chars),
853                 String.valueOf(token), p, fieldName));
854          }
855    
856          // Read the next token.  It must be either a comma (to indicate that
857          // there will be another field) or a closing curly brace (to indicate
858          // that the end of the object has been reached).
859          p = decodePos;
860          token = readToken(chars);
861          if (token.equals('}'))
862          {
863            return new JSONObject(fields);
864          }
865          else if (! token.equals(','))
866          {
867            throw new JSONException(ERR_OBJECT_EXPECTED_COMMA_OR_CLOSE_BRACE.get(
868                 new String(chars), String.valueOf(token), p));
869          }
870        }
871      }
872    
873    
874    
875      /**
876       * Retrieves a map of the fields contained in this JSON object.
877       *
878       * @return  A map of the fields contained in this JSON object.
879       */
880      public Map<String,JSONValue> getFields()
881      {
882        return fields;
883      }
884    
885    
886    
887      /**
888       * Retrieves the value for the specified field.
889       *
890       * @param  name  The name of the field for which to retrieve the value.  It
891       *               will be treated in a case-sensitive manner.
892       *
893       * @return  The value for the specified field, or {@code null} if the
894       *          requested field is not present in the JSON object.
895       */
896      public JSONValue getField(final String name)
897      {
898        return fields.get(name);
899      }
900    
901    
902    
903      /**
904       * {@inheritDoc}
905       */
906      @Override()
907      public int hashCode()
908      {
909        if (hashCode == null)
910        {
911          int hc = 0;
912          for (final Map.Entry<String,JSONValue> e : fields.entrySet())
913          {
914            hc += e.getKey().hashCode() + e.getValue().hashCode();
915          }
916    
917          hashCode = hc;
918        }
919    
920        return hashCode;
921      }
922    
923    
924    
925      /**
926       * {@inheritDoc}
927       */
928      @Override()
929      public boolean equals(final Object o)
930      {
931        if (o == this)
932        {
933          return true;
934        }
935    
936        if (o instanceof JSONObject)
937        {
938          final JSONObject obj = (JSONObject) o;
939          return fields.equals(obj.fields);
940        }
941    
942        return false;
943      }
944    
945    
946    
947      /**
948       * Indicates whether this JSON object is considered equal to the provided
949       * object, subject to the specified constraints.
950       *
951       * @param  o                    The object to compare against this JSON
952       *                              object.  It must not be {@code null}.
953       * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
954       *                              capitalization in field names.
955       * @param  ignoreValueCase      Indicates whether to ignore differences in
956       *                              capitalization in values that are JSON
957       *                              strings.
958       * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
959       *                              order of elements within an array.
960       *
961       * @return  {@code true} if this JSON object is considered equal to the
962       *          provided object (subject to the specified constraints), or
963       *          {@code false} if not.
964       */
965      public boolean equals(final JSONObject o, final boolean ignoreFieldNameCase,
966                            final boolean ignoreValueCase,
967                            final boolean ignoreArrayOrder)
968      {
969        // See if we can do a straight-up Map.equals.  If so, just do that.
970        if ((! ignoreFieldNameCase) && (! ignoreValueCase) && (! ignoreArrayOrder))
971        {
972          return fields.equals(o.fields);
973        }
974    
975        // Make sure they have the same number of fields.
976        if (fields.size() != o.fields.size())
977        {
978          return false;
979        }
980    
981        // Optimize for the case in which we field names are case sensitive.
982        if (! ignoreFieldNameCase)
983        {
984          for (final Map.Entry<String,JSONValue> e : fields.entrySet())
985          {
986            final JSONValue thisValue = e.getValue();
987            final JSONValue thatValue = o.fields.get(e.getKey());
988            if (thatValue == null)
989            {
990              return false;
991            }
992    
993            if (! thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
994                 ignoreArrayOrder))
995            {
996              return false;
997            }
998          }
999    
1000          return true;
1001        }
1002    
1003    
1004        // If we've gotten here, then we know that we need to treat field names in
1005        // a case-insensitive manner.  Create a new map that we can remove fields
1006        // from as we find matches.  This can help avoid false-positive matches in
1007        // which multiple fields in the first map match the same field in the second
1008        // map (e.g., because they have field names that differ only in case and
1009        // values that are logically equivalent).  It also makes iterating through
1010        // the values faster as we make more progress.
1011        final HashMap<String,JSONValue> thatMap =
1012             new HashMap<String,JSONValue>(o.fields);
1013        final Iterator<Map.Entry<String,JSONValue>> thisIterator =
1014             fields.entrySet().iterator();
1015        while (thisIterator.hasNext())
1016        {
1017          final Map.Entry<String,JSONValue> thisEntry = thisIterator.next();
1018          final String thisFieldName = thisEntry.getKey();
1019          final JSONValue thisValue = thisEntry.getValue();
1020    
1021          final Iterator<Map.Entry<String,JSONValue>> thatIterator =
1022               thatMap.entrySet().iterator();
1023    
1024          boolean found = false;
1025          while (thatIterator.hasNext())
1026          {
1027            final Map.Entry<String,JSONValue> thatEntry = thatIterator.next();
1028            final String thatFieldName = thatEntry.getKey();
1029            if (! thisFieldName.equalsIgnoreCase(thatFieldName))
1030            {
1031              continue;
1032            }
1033    
1034            final JSONValue thatValue = thatEntry.getValue();
1035            if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
1036                 ignoreArrayOrder))
1037            {
1038              found = true;
1039              thatIterator.remove();
1040              break;
1041            }
1042          }
1043    
1044          if (! found)
1045          {
1046            return false;
1047          }
1048        }
1049    
1050        return true;
1051      }
1052    
1053    
1054    
1055      /**
1056       * {@inheritDoc}
1057       */
1058      @Override()
1059      public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
1060                            final boolean ignoreValueCase,
1061                            final boolean ignoreArrayOrder)
1062      {
1063        return ((v instanceof JSONObject) &&
1064             equals((JSONObject) v, ignoreFieldNameCase, ignoreValueCase,
1065                  ignoreArrayOrder));
1066      }
1067    
1068    
1069    
1070      /**
1071       * {@inheritDoc}
1072       */
1073      @Override()
1074      public String toString()
1075      {
1076        if (stringRepresentation == null)
1077        {
1078          final StringBuilder buffer = new StringBuilder();
1079          toString(buffer);
1080          stringRepresentation = buffer.toString();
1081        }
1082    
1083        return stringRepresentation;
1084      }
1085    
1086    
1087    
1088      /**
1089       * {@inheritDoc}
1090       */
1091      @Override()
1092      public void toString(final StringBuilder buffer)
1093      {
1094        if (stringRepresentation != null)
1095        {
1096          buffer.append(stringRepresentation);
1097          return;
1098        }
1099    
1100        buffer.append("{ ");
1101    
1102        final Iterator<Map.Entry<String,JSONValue>> iterator =
1103             fields.entrySet().iterator();
1104        while (iterator.hasNext())
1105        {
1106          final Map.Entry<String,JSONValue> e = iterator.next();
1107          JSONString.encodeString(e.getKey(), buffer);
1108          buffer.append(':');
1109          e.getValue().toString(buffer);
1110    
1111          if (iterator.hasNext())
1112          {
1113            buffer.append(',');
1114          }
1115          buffer.append(' ');
1116        }
1117    
1118        buffer.append('}');
1119      }
1120    
1121    
1122    
1123      /**
1124       * Retrieves a string representation of this value as it should appear in a
1125       * JSON object formatted in a multi-line representation, including any
1126       * necessary quoting, escaping, etc.  The last line will not include a
1127       * trailing line break.
1128       *
1129       * @return A string representation of this value as it should appear in a
1130       *          JSON object formatted in a multi-line representation.
1131       */
1132      public String toMultiLineString()
1133      {
1134        final JSONBuffer jsonBuffer = new JSONBuffer(null, 0, true);
1135        appendToJSONBuffer(jsonBuffer);
1136        return jsonBuffer.toString();
1137      }
1138    
1139    
1140    
1141      /**
1142       * {@inheritDoc}
1143       */
1144      @Override()
1145      public String toSingleLineString()
1146      {
1147        final StringBuilder buffer = new StringBuilder();
1148        toSingleLineString(buffer);
1149        return buffer.toString();
1150      }
1151    
1152    
1153    
1154      /**
1155       * {@inheritDoc}
1156       */
1157      @Override()
1158      public void toSingleLineString(final StringBuilder buffer)
1159      {
1160        buffer.append("{ ");
1161    
1162        final Iterator<Map.Entry<String,JSONValue>> iterator =
1163             fields.entrySet().iterator();
1164        while (iterator.hasNext())
1165        {
1166          final Map.Entry<String,JSONValue> e = iterator.next();
1167          JSONString.encodeString(e.getKey(), buffer);
1168          buffer.append(':');
1169          e.getValue().toSingleLineString(buffer);
1170    
1171          if (iterator.hasNext())
1172          {
1173            buffer.append(',');
1174          }
1175          buffer.append(' ');
1176        }
1177    
1178        buffer.append('}');
1179      }
1180    
1181    
1182    
1183      /**
1184       * {@inheritDoc}
1185       */
1186      @Override()
1187      public String toNormalizedString()
1188      {
1189        final StringBuilder buffer = new StringBuilder();
1190        toNormalizedString(buffer);
1191        return buffer.toString();
1192      }
1193    
1194    
1195    
1196      /**
1197       * {@inheritDoc}
1198       */
1199      @Override()
1200      public void toNormalizedString(final StringBuilder buffer)
1201      {
1202        // The normalized representation needs to have the fields in a predictable
1203        // order, which we will accomplish using the lexicographic ordering that a
1204        // TreeMap will provide.  Field names will be case sensitive, but we still
1205        // need to construct a normalized way of escaping non-printable characters
1206        // in each field.
1207        final StringBuilder tempBuffer;
1208        if (decodeBuffer == null)
1209        {
1210          tempBuffer = new StringBuilder(20);
1211        }
1212        else
1213        {
1214          tempBuffer = decodeBuffer;
1215        }
1216    
1217        final TreeMap<String,String> m = new TreeMap<String,String>();
1218        for (final Map.Entry<String,JSONValue> e : fields.entrySet())
1219        {
1220          tempBuffer.setLength(0);
1221          tempBuffer.append('"');
1222          for (final char c : e.getKey().toCharArray())
1223          {
1224            if (StaticUtils.isPrintable(c))
1225            {
1226              tempBuffer.append(c);
1227            }
1228            else
1229            {
1230              tempBuffer.append("\\u");
1231              tempBuffer.append(String.format("%04X", (int) c));
1232            }
1233          }
1234          tempBuffer.append('"');
1235          final String normalizedKey = tempBuffer.toString();
1236    
1237          tempBuffer.setLength(0);
1238          e.getValue().toNormalizedString(tempBuffer);
1239          m.put(normalizedKey, tempBuffer.toString());
1240        }
1241    
1242        buffer.append('{');
1243        final Iterator<Map.Entry<String,String>> iterator = m.entrySet().iterator();
1244        while (iterator.hasNext())
1245        {
1246          final Map.Entry<String,String> e = iterator.next();
1247          buffer.append(e.getKey());
1248          buffer.append(':');
1249          buffer.append(e.getValue());
1250    
1251          if (iterator.hasNext())
1252          {
1253            buffer.append(',');
1254          }
1255        }
1256    
1257        buffer.append('}');
1258      }
1259    
1260    
1261    
1262      /**
1263       * {@inheritDoc}
1264       */
1265      @Override()
1266      public void appendToJSONBuffer(final JSONBuffer buffer)
1267      {
1268        buffer.beginObject();
1269    
1270        for (final Map.Entry<String,JSONValue> field : fields.entrySet())
1271        {
1272          final String name = field.getKey();
1273          final JSONValue value = field.getValue();
1274          value.appendToJSONBuffer(name, buffer);
1275        }
1276    
1277        buffer.endObject();
1278      }
1279    
1280    
1281    
1282      /**
1283       * {@inheritDoc}
1284       */
1285      @Override()
1286      public void appendToJSONBuffer(final String fieldName,
1287                                     final JSONBuffer buffer)
1288      {
1289        buffer.beginObject(fieldName);
1290    
1291        for (final Map.Entry<String,JSONValue> field : fields.entrySet())
1292        {
1293          final String name = field.getKey();
1294          final JSONValue value = field.getValue();
1295          value.appendToJSONBuffer(name, buffer);
1296        }
1297    
1298        buffer.endObject();
1299      }
1300    }