001    /*
002     * Copyright 2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2015 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       * Reads a token from the provided character array, skipping over any
232       * insignificant whitespace that may be before the token.  The token that is
233       * returned will be one of the following:
234       * <UL>
235       *   <LI>A {@code Character} that is an opening curly brace.</LI>
236       *   <LI>A {@code Character} that is a closing curly brace.</LI>
237       *   <LI>A {@code Character} that is an opening square bracket.</LI>
238       *   <LI>A {@code Character} that is a closing square bracket.</LI>
239       *   <LI>A {@code Character} that is a colon.</LI>
240       *   <LI>A {@code Character} that is a comma.</LI>
241       *   <LI>A {@link JSONBoolean}.</LI>
242       *   <LI>A {@link JSONNull}.</LI>
243       *   <LI>A {@link JSONNumber}.</LI>
244       *   <LI>A {@link JSONString}.</LI>
245       * </UL>
246       *
247       * @param  chars  The characters that comprise the string representation of
248       *                the JSON object.
249       *
250       * @return  The token that was read.
251       *
252       * @throws  JSONException  If a problem was encountered while reading the
253       *                         token.
254       */
255      private Object readToken(final char[] chars)
256              throws JSONException
257      {
258        skipWhitespace(chars);
259    
260        final char c = readCharacter(chars, false);
261        switch (c)
262        {
263          case '{':
264          case '}':
265          case '[':
266          case ']':
267          case ':':
268          case ',':
269            // This is a token character that we will return as-is.
270            decodePos++;
271            return c;
272    
273          case '"':
274            // This is the start of a JSON string.
275            return readString(chars);
276    
277          case 't':
278          case 'f':
279            // This is the start of a JSON true or false value.
280            return readBoolean(chars);
281    
282          case 'n':
283            // This is the start of a JSON null value.
284            return readNull(chars);
285    
286          case '-':
287          case '0':
288          case '1':
289          case '2':
290          case '3':
291          case '4':
292          case '5':
293          case '6':
294          case '7':
295          case '8':
296          case '9':
297            // This is the start of a JSON number value.
298            return readNumber(chars);
299    
300          default:
301            // This is not a valid JSON token.
302            throw new JSONException(ERR_OBJECT_INVALID_FIRST_TOKEN_CHAR.get(
303                 new String(chars), String.valueOf(c), decodePos));
304    
305        }
306      }
307    
308    
309    
310      /**
311       * Skips over any valid JSON whitespace at the current position in the
312       * provided array.
313       *
314       * @param  chars  The characters that comprise the string representation of
315       *                the JSON object.
316       *
317       * @throws  JSONException  If a problem is encountered while skipping
318       *                         whitespace.
319       */
320      private void skipWhitespace(final char[] chars)
321              throws JSONException
322      {
323        while (decodePos < chars.length)
324        {
325          switch (chars[decodePos])
326          {
327            // The space, tab, newline, and carriage return characters are
328            // considered valid JSON whitespace.
329            case ' ':
330            case '\t':
331            case '\n':
332            case '\r':
333              decodePos++;
334              break;
335    
336            // Technically, JSON does not provide support for comments.  But this
337            // implementation will accept two types of comments:
338            // - Comments that start with /* and end with */ (potentially spanning
339            //   multiple lines).
340            // - Comments that start with // and continue until the end of the line.
341            // All comments will be ignored by the parser.
342            case '/':
343              final int commentStartPos = decodePos;
344              if ((decodePos+1) >= chars.length)
345              {
346                return;
347              }
348              else if (chars[decodePos+1] == '/')
349              {
350                decodePos += 2;
351    
352                // Keep reading until we encounter a newline or carriage return, or
353                // until we hit the end of the string.
354                while (decodePos < chars.length)
355                {
356                  if ((chars[decodePos] == '\n') || (chars[decodePos] == '\r'))
357                  {
358                    break;
359                  }
360                  decodePos++;
361                }
362                break;
363              }
364              else if (chars[decodePos+1] == '*')
365              {
366                decodePos += 2;
367    
368                // Keep reading until we encounter "*/".  We must encounter "*/"
369                // before hitting the end of the string.
370                boolean closeFound = false;
371                while (decodePos < chars.length)
372                {
373                  if (chars[decodePos] == '*')
374                  {
375                    if (((decodePos+1) < chars.length) &&
376                        (chars[decodePos+1] == '/'))
377                    {
378                      closeFound = true;
379                      decodePos += 2;
380                      break;
381                    }
382                  }
383                  decodePos++;
384                }
385    
386                if (! closeFound)
387                {
388                  throw new JSONException(ERR_OBJECT_UNCLOSED_COMMENT.get(
389                       new String(chars), commentStartPos));
390                }
391                break;
392              }
393              else
394              {
395                return;
396              }
397    
398            default:
399              return;
400          }
401        }
402      }
403    
404    
405    
406      /**
407       * Reads the character at the specified position and optionally advances the
408       * position.
409       *
410       * @param  chars            The characters that comprise the string
411       *                          representation of the JSON object.
412       * @param  advancePosition  Indicates whether to advance the value of the
413       *                          position indicator after reading the character.
414       *                          If this is {@code false}, then this method will be
415       *                          used to "peek" at the next character without
416       *                          consuming it.
417       *
418       * @return  The character that was read.
419       *
420       * @throws  JSONException  If the end of the value was encountered when a
421       *                         character was expected.
422       */
423      private char readCharacter(final char[] chars, final boolean advancePosition)
424              throws JSONException
425      {
426        if (decodePos >= chars.length)
427        {
428          throw new JSONException(
429               ERR_OBJECT_UNEXPECTED_END_OF_STRING.get(new String(chars)));
430        }
431    
432        final char c = chars[decodePos];
433        if (advancePosition)
434        {
435          decodePos++;
436        }
437        return c;
438      }
439    
440    
441    
442      /**
443       * Reads a JSON string staring at the specified position in the provided
444       * character array.
445       *
446       * @param  chars  The characters that comprise the string representation of
447       *                the JSON object.
448       *
449       * @return  The JSON string that was read.
450       *
451       * @throws  JSONException  If a problem was encountered while reading the JSON
452       *                         string.
453       */
454      private JSONString readString(final char[] chars)
455              throws JSONException
456      {
457        // Create a buffer to hold the string.  Note that if we've gotten here then
458        // we already know that the character at the provided position is a quote,
459        // so we can read past it in the process.
460        final int startPos = decodePos++;
461        decodeBuffer.setLength(0);
462        while (true)
463        {
464          final char c = readCharacter(chars, true);
465          if (c == '\\')
466          {
467            final int escapedCharPos = decodePos;
468            final char escapedChar = readCharacter(chars, true);
469            switch (escapedChar)
470            {
471              case '"':
472              case '\\':
473              case '/':
474                decodeBuffer.append(escapedChar);
475                break;
476              case 'b':
477                decodeBuffer.append('\b');
478                break;
479              case 'f':
480                decodeBuffer.append('\f');
481                break;
482              case 'n':
483                decodeBuffer.append('\n');
484                break;
485              case 'r':
486                decodeBuffer.append('\r');
487                break;
488              case 't':
489                decodeBuffer.append('\t');
490                break;
491    
492              case 'u':
493                final char[] hexChars =
494                {
495                  readCharacter(chars, true),
496                  readCharacter(chars, true),
497                  readCharacter(chars, true),
498                  readCharacter(chars, true)
499                };
500                try
501                {
502                  decodeBuffer.append(
503                       (char) Integer.parseInt(new String(hexChars), 16));
504                }
505                catch (final Exception e)
506                {
507                  Debug.debugException(e);
508                  throw new JSONException(
509                       ERR_OBJECT_INVALID_UNICODE_ESCAPE.get(new String(chars),
510                            escapedCharPos),
511                       e);
512                }
513                break;
514    
515              default:
516                throw new JSONException(ERR_OBJECT_INVALID_ESCAPED_CHAR.get(
517                     new String(chars), escapedChar, escapedCharPos));
518            }
519          }
520          else if (c == '"')
521          {
522            return new JSONString(decodeBuffer.toString(),
523                 new String(chars, startPos, (decodePos - startPos)));
524          }
525          else
526          {
527            if (c <= '\u001F')
528            {
529              throw new JSONException(ERR_OBJECT_UNESCAPED_CONTROL_CHAR.get(
530                   new String(chars), String.format("%04X", (int) c),
531                   (decodePos - 1)));
532            }
533    
534            decodeBuffer.append(c);
535          }
536        }
537      }
538    
539    
540    
541      /**
542       * Reads a JSON Boolean staring at the specified position in the provided
543       * character array.
544       *
545       * @param  chars  The characters that comprise the string representation of
546       *                the JSON object.
547       *
548       * @return  The JSON Boolean that was read.
549       *
550       * @throws  JSONException  If a problem was encountered while reading the JSON
551       *                         Boolean.
552       */
553      private JSONBoolean readBoolean(final char[] chars)
554              throws JSONException
555      {
556        final int startPos = decodePos;
557        final char firstCharacter = readCharacter(chars, true);
558        if (firstCharacter == 't')
559        {
560          if ((readCharacter(chars, true) == 'r') &&
561              (readCharacter(chars, true) == 'u') &&
562              (readCharacter(chars, true) == 'e'))
563          {
564            return JSONBoolean.TRUE;
565          }
566        }
567        else if (firstCharacter == 'f')
568        {
569          if ((readCharacter(chars, true) == 'a') &&
570              (readCharacter(chars, true) == 'l') &&
571              (readCharacter(chars, true) == 's') &&
572              (readCharacter(chars, true) == 'e'))
573          {
574            return JSONBoolean.FALSE;
575          }
576        }
577    
578        throw new JSONException(ERR_OBJECT_UNABLE_TO_PARSE_BOOLEAN.get(
579             new String(chars), startPos));
580      }
581    
582    
583    
584      /**
585       * Reads a JSON null staring at the specified position in the provided
586       * character array.
587       *
588       * @param  chars  The characters that comprise the string representation of
589       *                the JSON object.
590       *
591       * @return  The JSON null that was read.
592       *
593       * @throws  JSONException  If a problem was encountered while reading the JSON
594       *                         null.
595       */
596      private JSONNull readNull(final char[] chars)
597              throws JSONException
598      {
599        final int startPos = decodePos;
600        if ((readCharacter(chars, true) == 'n') &&
601            (readCharacter(chars, true) == 'u') &&
602            (readCharacter(chars, true) == 'l') &&
603            (readCharacter(chars, true) == 'l'))
604        {
605          return JSONNull.NULL;
606        }
607    
608        throw new JSONException(ERR_OBJECT_UNABLE_TO_PARSE_NULL.get(
609             new String(chars), startPos));
610      }
611    
612    
613    
614      /**
615       * Reads a JSON number staring at the specified position in the provided
616       * character array.
617       *
618       * @param  chars  The characters that comprise the string representation of
619       *                the JSON object.
620       *
621       * @return  The JSON number that was read.
622       *
623       * @throws  JSONException  If a problem was encountered while reading the JSON
624       *                         number.
625       */
626      private JSONNumber readNumber(final char[] chars)
627              throws JSONException
628      {
629        // Read until we encounter whitespace, a comma, a closing square bracket, or
630        // a closing curly brace.  Then try to parse what we read as a number.
631        final int startPos = decodePos;
632        decodeBuffer.setLength(0);
633    
634        while (true)
635        {
636          final char c = readCharacter(chars, true);
637          switch (c)
638          {
639            case ' ':
640            case '\t':
641            case '\n':
642            case '\r':
643            case ',':
644            case ']':
645            case '}':
646              // We need to decrement the position indicator since the last one we
647              // read wasn't part of the number.
648              decodePos--;
649              return new JSONNumber(decodeBuffer.toString());
650    
651            default:
652              decodeBuffer.append(c);
653          }
654        }
655      }
656    
657    
658    
659      /**
660       * Reads a JSON array starting at the specified position in the provided
661       * character array.  Note that this method assumes that the opening square
662       * bracket has already been read.
663       *
664       * @param  chars  The characters that comprise the string representation of
665       *                the JSON object.
666       *
667       * @return  The JSON array that was read.
668       *
669       * @throws  JSONException  If a problem was encountered while reading the JSON
670       *                         array.
671       */
672      private JSONArray readArray(final char[] chars)
673              throws JSONException
674      {
675        // The opening square bracket will have already been consumed, so read
676        // JSON values until we hit a closing square bracket.
677        final ArrayList<JSONValue> values = new ArrayList<JSONValue>(10);
678        boolean firstToken = true;
679        while (true)
680        {
681          // If this is the first time through, it is acceptable to find a closing
682          // square bracket.  Otherwise, we expect to find a JSON value, an opening
683          // square bracket to denote the start of an embedded array, or an opening
684          // curly brace to denote the start of an embedded JSON object.
685          int p = decodePos;
686          Object token = readToken(chars);
687          if (token instanceof JSONValue)
688          {
689            values.add((JSONValue) token);
690          }
691          else if (token.equals('['))
692          {
693            values.add(readArray(chars));
694          }
695          else if (token.equals('{'))
696          {
697            final LinkedHashMap<String,JSONValue> fieldMap =
698                 new LinkedHashMap<String,JSONValue>(10);
699            values.add(readObject(chars, fieldMap));
700          }
701          else if (token.equals(']') && firstToken)
702          {
703            // It's an empty array.
704            return JSONArray.EMPTY_ARRAY;
705          }
706          else
707          {
708            throw new JSONException(
709                 ERR_OBJECT_INVALID_TOKEN_WHEN_ARRAY_VALUE_EXPECTED.get(
710                      new String(chars), String.valueOf(token), p));
711          }
712    
713          firstToken = false;
714    
715    
716          // If we've gotten here, then we found a JSON value.  It must be followed
717          // by either a comma (to indicate that there's at least one more value) or
718          // a closing square bracket (to denote the end of the array).
719          p = decodePos;
720          token = readToken(chars);
721          if (token.equals(']'))
722          {
723            return new JSONArray(values);
724          }
725          else if (! token.equals(','))
726          {
727            throw new JSONException(
728                 ERR_OBJECT_INVALID_TOKEN_WHEN_ARRAY_COMMA_OR_BRACKET_EXPECTED.get(
729                      new String(chars), String.valueOf(token), p));
730          }
731        }
732      }
733    
734    
735    
736      /**
737       * Reads a JSON object starting at the specified position in the provided
738       * character array.  Note that this method assumes that the opening curly
739       * brace has already been read.
740       *
741       * @param  chars   The characters that comprise the string representation of
742       *                 the JSON object.
743       * @param  fields  The map into which to place the fields that are read.  The
744       *                 returned object will include an unmodifiable view of this
745       *                 map, but the caller may use the map directly if desired.
746       *
747       * @return  The JSON object that was read.
748       *
749       * @throws  JSONException  If a problem was encountered while reading the JSON
750       *                         object.
751       */
752      private JSONObject readObject(final char[] chars,
753                                    final Map<String,JSONValue> fields)
754              throws JSONException
755      {
756        boolean firstField = true;
757        while (true)
758        {
759          // Read the next token.  It must be a JSONString, unless we haven't read
760          // any fields yet in which case it can be a closing curly brace to
761          // indicate that it's an empty object.
762          int p = decodePos;
763          final String fieldName;
764          Object token = readToken(chars);
765          if (token instanceof JSONString)
766          {
767            fieldName = ((JSONString) token).stringValue();
768            if (fields.containsKey(fieldName))
769            {
770              throw new JSONException(ERR_OBJECT_DUPLICATE_FIELD.get(
771                   new String(chars), fieldName));
772            }
773          }
774          else if (firstField && token.equals('}'))
775          {
776            return new JSONObject(fields);
777          }
778          else
779          {
780            throw new JSONException(ERR_OBJECT_EXPECTED_STRING.get(
781                 new String(chars), String.valueOf(token), p));
782          }
783          firstField = false;
784    
785          // Read the next token.  It must be a colon.
786          p = decodePos;
787          token = readToken(chars);
788          if (! token.equals(':'))
789          {
790            throw new JSONException(ERR_OBJECT_EXPECTED_COLON.get(new String(chars),
791                 String.valueOf(token), p));
792          }
793    
794          // Read the next token.  It must be one of the following:
795          // - A JSONValue
796          // - An opening square bracket, designating the start of an array.
797          // - An opening curly brace, designating the start of an object.
798          p = decodePos;
799          token = readToken(chars);
800          if (token instanceof JSONValue)
801          {
802            fields.put(fieldName, (JSONValue) token);
803          }
804          else if (token.equals('['))
805          {
806            final JSONArray a = readArray(chars);
807            fields.put(fieldName, a);
808          }
809          else if (token.equals('{'))
810          {
811            final LinkedHashMap<String,JSONValue> m =
812                 new LinkedHashMap<String,JSONValue>(10);
813            final JSONObject o = readObject(chars, m);
814            fields.put(fieldName, o);
815          }
816          else
817          {
818            throw new JSONException(ERR_OBJECT_EXPECTED_VALUE.get(new String(chars),
819                 String.valueOf(token), p, fieldName));
820          }
821    
822          // Read the next token.  It must be either a comma (to indicate that
823          // there will be another field) or a closing curly brace (to indicate
824          // that the end of the object has been reached).
825          p = decodePos;
826          token = readToken(chars);
827          if (token.equals('}'))
828          {
829            return new JSONObject(fields);
830          }
831          else if (! token.equals(','))
832          {
833            throw new JSONException(ERR_OBJECT_EXPECTED_COMMA_OR_CLOSE_BRACE.get(
834                 new String(chars), String.valueOf(token), p));
835          }
836        }
837      }
838    
839    
840    
841      /**
842       * Retrieves a map of the fields contained in this JSON object.
843       *
844       * @return  A map of the fields contained in this JSON object.
845       */
846      public Map<String,JSONValue> getFields()
847      {
848        return fields;
849      }
850    
851    
852    
853      /**
854       * Retrieves the value for the specified field.
855       *
856       * @param  name  The name of the field for which to retrieve the value.  It
857       *               will be treated in a case-sensitive manner.
858       *
859       * @return  The value for the specified field, or {@code null} if the
860       *          requested field is not present in the JSON object.
861       */
862      public JSONValue getField(final String name)
863      {
864        return fields.get(name);
865      }
866    
867    
868    
869      /**
870       * {@inheritDoc}
871       */
872      @Override()
873      public int hashCode()
874      {
875        if (hashCode == null)
876        {
877          int hc = 0;
878          for (final Map.Entry<String,JSONValue> e : fields.entrySet())
879          {
880            hc += e.getKey().hashCode() + e.getValue().hashCode();
881          }
882    
883          hashCode = hc;
884        }
885    
886        return hashCode;
887      }
888    
889    
890    
891      /**
892       * {@inheritDoc}
893       */
894      @Override()
895      public boolean equals(final Object o)
896      {
897        if (o == this)
898        {
899          return true;
900        }
901    
902        if (o instanceof JSONObject)
903        {
904          final JSONObject obj = (JSONObject) o;
905          return fields.equals(obj.fields);
906        }
907    
908        return false;
909      }
910    
911    
912    
913      /**
914       * Indicates whether this JSON object is considered equal to the provided
915       * object, subject to the specified constraints.
916       *
917       * @param  o                    The object to compare against this JSON
918       *                              object.  It must not be {@code null}.
919       * @param  ignoreFieldNameCase  Indicates whether to ignore differences in
920       *                              capitalization in field names.
921       * @param  ignoreValueCase      Indicates whether to ignore differences in
922       *                              capitalization in values that are JSON
923       *                              strings.
924       * @param  ignoreArrayOrder     Indicates whether to ignore differences in the
925       *                              order of elements within an array.
926       *
927       * @return  {@code true} if this JSON object is considered equal to the
928       *          provided object (subject to the specified constraints), or
929       *          {@code false} if not.
930       */
931      public boolean equals(final JSONObject o, final boolean ignoreFieldNameCase,
932                            final boolean ignoreValueCase,
933                            final boolean ignoreArrayOrder)
934      {
935        // See if we can do a straight-up Map.equals.  If so, just do that.
936        if ((! ignoreFieldNameCase) && (! ignoreValueCase) && (! ignoreArrayOrder))
937        {
938          return fields.equals(o.fields);
939        }
940    
941        // Make sure they have the same number of fields.
942        if (fields.size() != o.fields.size())
943        {
944          return false;
945        }
946    
947        // Optimize for the case in which we field names are case sensitive.
948        if (! ignoreFieldNameCase)
949        {
950          for (final Map.Entry<String,JSONValue> e : fields.entrySet())
951          {
952            final JSONValue thisValue = e.getValue();
953            final JSONValue thatValue = o.fields.get(e.getKey());
954            if (thatValue == null)
955            {
956              return false;
957            }
958    
959            if (! thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
960                 ignoreArrayOrder))
961            {
962              return false;
963            }
964          }
965    
966          return true;
967        }
968    
969    
970        // If we've gotten here, then we know that we need to treat field names in
971        // a case-insensitive manner.  Create a new map that we can remove fields
972        // from as we find matches.  This can help avoid false-positive matches in
973        // which multiple fields in the first map match the same field in the second
974        // map (e.g., because they have field names that differ only in case and
975        // values that are logically equivalent).  It also makes iterating through
976        // the values faster as we make more progress.
977        final HashMap<String,JSONValue> thatMap =
978             new HashMap<String,JSONValue>(o.fields);
979        final Iterator<Map.Entry<String,JSONValue>> thisIterator =
980             fields.entrySet().iterator();
981        while (thisIterator.hasNext())
982        {
983          final Map.Entry<String,JSONValue> thisEntry = thisIterator.next();
984          final String thisFieldName = thisEntry.getKey();
985          final JSONValue thisValue = thisEntry.getValue();
986    
987          final Iterator<Map.Entry<String,JSONValue>> thatIterator =
988               thatMap.entrySet().iterator();
989    
990          boolean found = false;
991          while (thatIterator.hasNext())
992          {
993            final Map.Entry<String,JSONValue> thatEntry = thatIterator.next();
994            final String thatFieldName = thatEntry.getKey();
995            if (! thisFieldName.equalsIgnoreCase(thatFieldName))
996            {
997              continue;
998            }
999    
1000            final JSONValue thatValue = thatEntry.getValue();
1001            if (thisValue.equals(thatValue, ignoreFieldNameCase, ignoreValueCase,
1002                 ignoreArrayOrder))
1003            {
1004              found = true;
1005              thatIterator.remove();
1006              break;
1007            }
1008          }
1009    
1010          if (! found)
1011          {
1012            return false;
1013          }
1014        }
1015    
1016        return true;
1017      }
1018    
1019    
1020    
1021      /**
1022       * {@inheritDoc}
1023       */
1024      @Override()
1025      public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
1026                            final boolean ignoreValueCase,
1027                            final boolean ignoreArrayOrder)
1028      {
1029        return ((v instanceof JSONObject) &&
1030             equals((JSONObject) v, ignoreFieldNameCase, ignoreValueCase,
1031                  ignoreArrayOrder));
1032      }
1033    
1034    
1035    
1036      /**
1037       * {@inheritDoc}
1038       */
1039      @Override()
1040      public String toString()
1041      {
1042        if (stringRepresentation == null)
1043        {
1044          final StringBuilder buffer = new StringBuilder();
1045          toString(buffer);
1046          stringRepresentation = buffer.toString();
1047        }
1048    
1049        return stringRepresentation;
1050      }
1051    
1052    
1053    
1054      /**
1055       * {@inheritDoc}
1056       */
1057      @Override()
1058      public void toString(final StringBuilder buffer)
1059      {
1060        if (stringRepresentation != null)
1061        {
1062          buffer.append(stringRepresentation);
1063          return;
1064        }
1065    
1066        buffer.append("{ ");
1067    
1068        final Iterator<Map.Entry<String,JSONValue>> iterator =
1069             fields.entrySet().iterator();
1070        while (iterator.hasNext())
1071        {
1072          final Map.Entry<String,JSONValue> e = iterator.next();
1073          JSONString.encodeString(e.getKey(), buffer);
1074          buffer.append(':');
1075          e.getValue().toString(buffer);
1076    
1077          if (iterator.hasNext())
1078          {
1079            buffer.append(',');
1080          }
1081          buffer.append(' ');
1082        }
1083    
1084        buffer.append('}');
1085      }
1086    
1087    
1088    
1089      /**
1090       * {@inheritDoc}
1091       */
1092      @Override()
1093      public String toNormalizedString()
1094      {
1095        final StringBuilder buffer = new StringBuilder();
1096        toNormalizedString(buffer);
1097        return buffer.toString();
1098      }
1099    
1100    
1101    
1102      /**
1103       * {@inheritDoc}
1104       */
1105      @Override()
1106      public void toNormalizedString(final StringBuilder buffer)
1107      {
1108        // The normalized representation needs to have the fields in a predictable
1109        // order, which we will accomplish using the lexicographic ordering that a
1110        // TreeMap will provide.  Field names will be case sensitive, but we still
1111        // need to construct a normalized way of escaping non-printable characters
1112        // in each field.
1113        final StringBuilder tempBuffer;
1114        if (decodeBuffer == null)
1115        {
1116          tempBuffer = new StringBuilder(20);
1117        }
1118        else
1119        {
1120          tempBuffer = decodeBuffer;
1121        }
1122    
1123        final TreeMap<String,String> m = new TreeMap<String,String>();
1124        for (final Map.Entry<String,JSONValue> e : fields.entrySet())
1125        {
1126          tempBuffer.setLength(0);
1127          tempBuffer.append('"');
1128          for (final char c : e.getKey().toCharArray())
1129          {
1130            if (StaticUtils.isPrintable(c))
1131            {
1132              tempBuffer.append(c);
1133            }
1134            else
1135            {
1136              tempBuffer.append("\\u");
1137              tempBuffer.append(String.format("%04X", (int) c));
1138            }
1139          }
1140          tempBuffer.append('"');
1141          final String normalizedKey = tempBuffer.toString();
1142    
1143          tempBuffer.setLength(0);
1144          e.getValue().toNormalizedString(tempBuffer);
1145          m.put(normalizedKey, tempBuffer.toString());
1146        }
1147    
1148        buffer.append('{');
1149        final Iterator<Map.Entry<String,String>> iterator = m.entrySet().iterator();
1150        while (iterator.hasNext())
1151        {
1152          final Map.Entry<String,String> e = iterator.next();
1153          buffer.append(e.getKey());
1154          buffer.append(':');
1155          buffer.append(e.getValue());
1156    
1157          if (iterator.hasNext())
1158          {
1159            buffer.append(',');
1160          }
1161        }
1162    
1163        buffer.append('}');
1164      }
1165    }