001    // Copyright 2007, 2010, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.json;
016    
017    /*
018     * Copyright (c) 2002 JSON.org
019     * Permission is hereby granted, free of charge, to any person obtaining a copy
020     * of this software and associated documentation files (the "Software"), to deal
021     * in the Software without restriction, including without limitation the rights
022     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
023     * copies of the Software, and to permit persons to whom the Software is
024     * furnished to do so, subject to the following conditions:
025     * The above copyright notice and this permission notice shall be included in all
026     * copies or substantial portions of the Software.
027     * The Software shall be used for Good, not Evil.
028     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
029     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
030     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
031     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
032     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
033     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
034     * SOFTWARE.
035     */
036    
037    import java.util.*;
038    
039    /**
040     * A JSONObject is an unordered collection of name/value pairs. Its external form is a string wrapped in curly braces
041     * with colons between the names and values, and commas between the values and names. The internal form is an object
042     * having <code>get</code> and <code>opt</code> methods for accessing the values by name, and <code>put</code> methods
043     * for adding or replacing values by name. The values can be any of these types: <code>Boolean</code>,
044     * {@link org.apache.tapestry5.json.JSONArray}, {@link org.apache.tapestry5.json.JSONLiteral}, <code>JSONObject</code>,
045     * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject constructor can be
046     * used to convert an external form JSON text into
047     * an internal form whose values can be retrieved with the <code>get</code> and <code>opt</code> methods, or to convert
048     * values into a JSON text using the <code>put</code> and <code>toString</code> methods. A <code>get</code> method
049     * returns a value if one can be found, and throws an exception if one cannot be found. An <code>opt</code> method
050     * returns a default value instead of throwing an exception, and so is useful for obtaining optional values.
051     * <p/>
052     * The generic <code>get()</code> and <code>opt()</code> methods return an object, which you can cast or query for type.
053     * There are also typed <code>get</code> and <code>opt</code> methods that do type checking and type coersion for you.
054     * <p/>
055     * The <code>put</code> methods adds values to an object. For example,
056     * <p/>
057     * <p/>
058     * <pre>
059     * myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
060     * </pre>
061     * <p/>
062     * produces the string <code>{"JSON": "Hello, World"}</code>.
063     * <p/>
064     * The texts produced by the <code>toString</code> methods strictly conform to the JSON syntax rules. The constructors
065     * are more forgiving in the texts they will accept:
066     * <ul>
067     * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just before the closing brace.</li>
068     * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
069     * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote, and if they do not
070     * contain leading or trailing spaces, and if they do not contain any of these characters: <code>{ }
071     * [ ] / \ : , = ; #</code> and if they do not look like numbers and if they are not the reserved words
072     * <code>true</code>, <code>false</code>, or <code>null</code>.</li>
073     * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by <code>:</code>.</li>
074     * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as well as by <code>,</code>
075     * <small>(comma)</small>.</li>
076     * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or <code>0x-</code> <small>(hex)</small> prefix.</li>
077     * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li>
078     * </ul>
079     * <hr/>
080     * <p/>
081     * This class, and the other related classes, have been heavily modified from the original source, to fit Tapestry
082     * standards and to make use of JDK 1.5 features such as generics. Further, since the interest of Tapestry is primarily
083     * constructing JSON (and not parsing it), many of the non-essential methods have been removed (since the original code
084     * came with no tests).
085     * <p/>
086     * Finally, support for the {@link org.apache.tapestry5.json.JSONLiteral} type has been added, which allow the exact
087     * output to be controlled; useful when a JSONObject is being used as a configuration object, and must contain values
088     * that are not simple data, such as an inline function (technically making the result not JSON).
089     *
090     * @author JSON.org
091     * @version 2
092     */
093    @SuppressWarnings(
094            {"CloneDoesntCallSuperClone"})
095    public final class JSONObject extends JSONCollection
096    {
097    
098        /**
099         * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is equivalent to the
100         * value that JavaScript calls undefined.
101         */
102        private static final class Null implements JSONString
103        {
104            /**
105             * A Null object is equal to the null value and to itself.
106             *
107             * @param object An object to test for nullness.
108             * @return true if the object parameter is the JSONObject.NULL object or null.
109             */
110            @Override
111            public boolean equals(Object object)
112            {
113                return object == null || object == this;
114            }
115    
116            /**
117             * Get the "null" string value.
118             *
119             * @return The string "null".
120             */
121            @Override
122            public String toString()
123            {
124                return "null";
125            }
126    
127            public String toJSONString()
128            {
129                return "null";
130            }
131        }
132    
133        /**
134         * The map where the JSONObject's properties are kept.
135         */
136        private final Map<String, Object> properties = new HashMap<String, Object>();
137    
138        /**
139         * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than to use Java's
140         * <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
141         * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
142         */
143        public static final Object NULL = new Null();
144    
145        /**
146         * Construct an empty JSONObject.
147         */
148        public JSONObject()
149        {
150        }
151    
152        /**
153         * Constructs a new JSONObject using a series of String keys and values.
154         *
155         * @since 5.2.0
156         */
157        public JSONObject(String... keysAndValues)
158        {
159            int i = 0;
160    
161            while (i < keysAndValues.length)
162            {
163                put(keysAndValues[i++], keysAndValues[i++]);
164            }
165        }
166    
167        /**
168         * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to identify the keys that
169         * should be copied. Missing keys are ignored.
170         *
171         * @param source        A JSONObject.
172         * @param propertyNames The strings to copy.
173         * @throws RuntimeException If a value is a non-finite number.
174         */
175        public JSONObject(JSONObject source, String... propertyNames)
176        {
177            for (String name : propertyNames)
178            {
179                Object value = source.opt(name);
180    
181                if (value != null)
182                    put(name, value);
183            }
184        }
185    
186        /**
187         * Construct a JSONObject from a JSONTokener.
188         *
189         * @param x A JSONTokener object containing the source string. @ If there is a syntax error in the source string.
190         */
191        JSONObject(JSONTokener x)
192        {
193            String key;
194    
195            if (x.nextClean() != '{')
196            {
197                throw x.syntaxError("A JSONObject text must begin with '{'");
198            }
199    
200            while (true)
201            {
202                char c = x.nextClean();
203                switch (c)
204                {
205                    case 0:
206                        throw x.syntaxError("A JSONObject text must end with '}'");
207                    case '}':
208                        return;
209                    default:
210                        x.back();
211                        key = x.nextValue().toString();
212                }
213    
214                /*
215                 * The key is followed by ':'. We will also tolerate '=' or '=>'.
216                 */
217    
218                c = x.nextClean();
219                if (c == '=')
220                {
221                    if (x.next() != '>')
222                    {
223                        x.back();
224                    }
225                } else if (c != ':')
226                {
227                    throw x.syntaxError("Expected a ':' after a key");
228                }
229                put(key, x.nextValue());
230    
231                /*
232                 * Pairs are separated by ','. We will also tolerate ';'.
233                 */
234    
235                switch (x.nextClean())
236                {
237                    case ';':
238                    case ',':
239                        if (x.nextClean() == '}')
240                        {
241                            return;
242                        }
243                        x.back();
244                        break;
245                    case '}':
246                        return;
247                    default:
248                        throw x.syntaxError("Expected a ',' or '}'");
249                }
250            }
251        }
252    
253        /**
254         * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor.
255         *
256         * @param string A string beginning with <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>
257         *               &nbsp;<small>(right brace)</small>.
258         * @throws RuntimeException If there is a syntax error in the source string.
259         */
260        public JSONObject(String string)
261        {
262            this(new JSONTokener(string));
263        }
264    
265        /**
266         * Accumulate values under a key. It is similar to the put method except that if there is already an object stored
267         * under the key then a JSONArray is stored under the key to hold all of the accumulated values. If there is already
268         * a JSONArray, then the new value is appended to it. In contrast, the put method replaces the previous value.
269         *
270         * @param key   A key string.
271         * @param value An object to be accumulated under the key.
272         * @return this.
273         * @throws {@link RuntimeException} If the value is an invalid number or if the key is null.
274         */
275        public JSONObject accumulate(String key, Object value)
276        {
277            testValidity(value);
278    
279            Object existing = opt(key);
280    
281            if (existing == null)
282            {
283                // Note that the original implementation of this method contradicited the method
284                // documentation.
285                put(key, value);
286                return this;
287            }
288    
289            if (existing instanceof JSONArray)
290            {
291                ((JSONArray) existing).put(value);
292                return this;
293            }
294    
295            // Replace the existing value, of any type, with an array that includes both the
296            // existing and the new value.
297    
298            put(key, new JSONArray().put(existing).put(value));
299    
300            return this;
301        }
302    
303        /**
304         * Append values to the array under a key. If the key does not exist in the JSONObject, then the key is put in the
305         * JSONObject with its value being a JSONArray containing the value parameter. If the key was already associated
306         * with a JSONArray, then the value parameter is appended to it.
307         *
308         * @param key   A key string.
309         * @param value An object to be accumulated under the key.
310         * @return this. @ If the key is null or if the current value associated with the key is not a JSONArray.
311         */
312        public JSONObject append(String key, Object value)
313        {
314            testValidity(value);
315            Object o = opt(key);
316            if (o == null)
317            {
318                put(key, new JSONArray().put(value));
319            } else if (o instanceof JSONArray)
320            {
321                put(key, ((JSONArray) o).put(value));
322            } else
323            {
324                throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
325            }
326    
327            return this;
328        }
329    
330        /**
331         * Produce a string from a double. The string "null" will be returned if the number is not finite.
332         *
333         * @param d A double.
334         * @return A String.
335         */
336        static String doubleToString(double d)
337        {
338            if (Double.isInfinite(d) || Double.isNaN(d))
339            {
340                return "null";
341            }
342    
343            // Shave off trailing zeros and decimal point, if possible.
344    
345            String s = Double.toString(d);
346            if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
347            {
348                while (s.endsWith("0"))
349                {
350                    s = s.substring(0, s.length() - 1);
351                }
352                if (s.endsWith("."))
353                {
354                    s = s.substring(0, s.length() - 1);
355                }
356            }
357            return s;
358        }
359    
360        /**
361         * Get the value object associated with a key.
362         *
363         * @param key A key string.
364         * @return The object associated with the key.
365         * @throws RuntimeException if the key is not found.
366         * @see #opt(String)
367         */
368        public Object get(String key)
369        {
370            Object o = opt(key);
371            if (o == null)
372            {
373                throw new RuntimeException("JSONObject[" + quote(key) + "] not found.");
374            }
375    
376            return o;
377        }
378    
379        /**
380         * Get the boolean value associated with a key.
381         *
382         * @param key A key string.
383         * @return The truth.
384         * @throws RuntimeException if the value does not exist, is not a Boolean or the String "true" or "false".
385         */
386        public boolean getBoolean(String key)
387        {
388            Object o = get(key);
389    
390            if (o instanceof Boolean)
391                return o.equals(Boolean.TRUE);
392    
393            if (o instanceof String)
394            {
395                String value = (String) o;
396    
397                if (value.equalsIgnoreCase("true"))
398                    return true;
399    
400                if (value.equalsIgnoreCase("false"))
401                    return false;
402            }
403    
404            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean.");
405        }
406    
407        /**
408         * Get the double value associated with a key.
409         *
410         * @param key A key string.
411         * @return The numeric value. @throws RuntimeException if the key is not found or if the value is not a Number object and cannot be
412         *         converted to a number.
413         */
414        public double getDouble(String key)
415        {
416            Object value = get(key);
417    
418            try
419            {
420                if (value instanceof Number)
421                    return ((Number) value).doubleValue();
422    
423                // This is a bit sloppy for the case where value is not a string.
424    
425                return Double.valueOf((String) value);
426            } catch (Exception e)
427            {
428                throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number.");
429            }
430        }
431    
432        /**
433         * Get the int value associated with a key. If the number value is too large for an int, it will be clipped.
434         *
435         * @param key A key string.
436         * @return The integer value.
437         * @throws RuntimeException if the key is not found or if the value cannot be converted to an integer.
438         */
439        public int getInt(String key)
440        {
441            Object value = get(key);
442    
443            if (value instanceof Number)
444                return ((Number) value).intValue();
445    
446            // Very inefficient way to do this!
447            return (int) getDouble(key);
448        }
449    
450        /**
451         * Get the JSONArray value associated with a key.
452         *
453         * @param key A key string.
454         * @return A JSONArray which is the value.
455         * @throws RuntimeException if the key is not found or if the value is not a JSONArray.
456         */
457        public JSONArray getJSONArray(String key)
458        {
459            Object o = get(key);
460            if (o instanceof JSONArray)
461            {
462                return (JSONArray) o;
463            }
464    
465            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
466        }
467    
468        /**
469         * Get the JSONObject value associated with a key.
470         *
471         * @param key A key string.
472         * @return A JSONObject which is the value.
473         * @throws RuntimeException if the key is not found or if the value is not a JSONObject.
474         */
475        public JSONObject getJSONObject(String key)
476        {
477            Object o = get(key);
478            if (o instanceof JSONObject)
479            {
480                return (JSONObject) o;
481            }
482    
483            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject.");
484        }
485    
486        /**
487         * Get the long value associated with a key. If the number value is too long for a long, it will be clipped.
488         *
489         * @param key A key string.
490         * @return The long value.
491         * @throws RuntimeException if the key is not found or if the value cannot be converted to a long.
492         */
493        public long getLong(String key)
494        {
495            Object o = get(key);
496            return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
497        }
498    
499        /**
500         * Get the string associated with a key.
501         *
502         * @param key A key string.
503         * @return A string which is the value.
504         * @throws RuntimeException if the key is not found.
505         */
506        public String getString(String key)
507        {
508            return get(key).toString();
509        }
510    
511        /**
512         * Determine if the JSONObject contains a specific key.
513         *
514         * @param key A key string.
515         * @return true if the key exists in the JSONObject.
516         */
517        public boolean has(String key)
518        {
519            return properties.containsKey(key);
520        }
521    
522        /**
523         * Determine if the value associated with the key is null or if there is no value.
524         *
525         * @param key A key string.
526         * @return true if there is no value associated with the key or if the value is the JSONObject.NULL object.
527         */
528        public boolean isNull(String key)
529        {
530            return JSONObject.NULL.equals(opt(key));
531        }
532    
533        /**
534         * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified.
535         *
536         * @return An iterator of the keys.
537         */
538        public Set<String> keys()
539        {
540            return properties.keySet();
541        }
542    
543        /**
544         * Get the number of keys stored in the JSONObject.
545         *
546         * @return The number of keys in the JSONObject.
547         */
548        public int length()
549        {
550            return properties.size();
551        }
552    
553        /**
554         * Produce a JSONArray containing the names of the elements of this JSONObject.
555         *
556         * @return A JSONArray containing the key strings, or null if the JSONObject is empty.
557         */
558        public JSONArray names()
559        {
560            JSONArray ja = new JSONArray();
561    
562            for (String key : keys())
563            {
564                ja.put(key);
565            }
566    
567            return ja.length() == 0 ? null : ja;
568        }
569    
570        /**
571         * Produce a string from a Number.
572         *
573         * @param n A Number
574         * @return A String. @ If n is a non-finite number.
575         */
576        static String numberToString(Number n)
577        {
578            assert n != null;
579    
580            testValidity(n);
581    
582            // Shave off trailing zeros and decimal point, if possible.
583    
584            String s = n.toString();
585            if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
586            {
587                while (s.endsWith("0"))
588                {
589                    s = s.substring(0, s.length() - 1);
590                }
591                if (s.endsWith("."))
592                {
593                    s = s.substring(0, s.length() - 1);
594                }
595            }
596            return s;
597        }
598    
599        /**
600         * Get an optional value associated with a key.
601         *
602         * @param key A key string.
603         * @return An object which is the value, or null if there is no value.
604         * @see #get(String)
605         */
606        public Object opt(String key)
607        {
608            return properties.get(key);
609        }
610    
611        /**
612         * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from the JSONObject if
613         * it is present.
614         *
615         * @param key   A key string.
616         * @param value An object which is the value. It should be of one of these types: Boolean, Double, Integer,
617         *              JSONArray, JSONObject, JSONLiteral, Long, String, or the JSONObject.NULL object.
618         * @return this.
619         * @throws RuntimeException If the value is non-finite number or if the key is null.
620         */
621        public JSONObject put(String key, Object value)
622        {
623            assert key != null;
624    
625            if (value != null)
626            {
627                testValidity(value);
628                properties.put(key, value);
629            } else
630            {
631                remove(key);
632            }
633    
634            return this;
635        }
636    
637        /**
638         * Produce a string in double quotes with backslash sequences in all the right places. A backslash will be inserted
639         * within </, allowing JSON text to be delivered in HTML. In JSON text, a string cannot contain a control character
640         * or an unescaped quote or backslash.
641         *
642         * @param string A String
643         * @return A String correctly formatted for insertion in a JSON text.
644         */
645        public static String quote(String string)
646        {
647            if (string == null || string.length() == 0)
648            {
649                return "\"\"";
650            }
651    
652            char b;
653            char c = 0;
654            int i;
655            int len = string.length();
656            StringBuilder buffer = new StringBuilder(len + 4);
657            String t;
658    
659            buffer.append('"');
660            for (i = 0; i < len; i += 1)
661            {
662                b = c;
663                c = string.charAt(i);
664                switch (c)
665                {
666                    case '\\':
667                    case '"':
668                        buffer.append('\\');
669                        buffer.append(c);
670                        break;
671                    case '/':
672                        if (b == '<')
673                        {
674                            buffer.append('\\');
675                        }
676                        buffer.append(c);
677                        break;
678                    case '\b':
679                        buffer.append("\\b");
680                        break;
681                    case '\t':
682                        buffer.append("\\t");
683                        break;
684                    case '\n':
685                        buffer.append("\\n");
686                        break;
687                    case '\f':
688                        buffer.append("\\f");
689                        break;
690                    case '\r':
691                        buffer.append("\\r");
692                        break;
693                    default:
694                        if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100'))
695                        {
696                            t = "000" + Integer.toHexString(c);
697                            buffer.append("\\u").append(t.substring(t.length() - 4));
698                        } else
699                        {
700                            buffer.append(c);
701                        }
702                }
703            }
704            buffer.append('"');
705            return buffer.toString();
706        }
707    
708        /**
709         * Remove a name and its value, if present.
710         *
711         * @param key The name to be removed.
712         * @return The value that was associated with the name, or null if there was no value.
713         */
714        public Object remove(String key)
715        {
716            return properties.remove(key);
717        }
718    
719        private static final Class[] ALLOWED = new Class[]
720                {String.class, Boolean.class, Number.class, JSONObject.class, JSONArray.class, JSONString.class,
721                        JSONLiteral.class, Null.class};
722    
723        /**
724         * Throw an exception if the object is an NaN or infinite number, or not a type which may be stored.
725         *
726         * @param value The object to test. @ If o is a non-finite number.
727         */
728        @SuppressWarnings("unchecked")
729        static void testValidity(Object value)
730        {
731            if (value == null)
732                return;
733    
734            boolean found = false;
735            Class actual = value.getClass();
736    
737            for (Class allowed : ALLOWED)
738            {
739                if (allowed.isAssignableFrom(actual))
740                {
741                    found = true;
742                    break;
743                }
744            }
745    
746            if (!found)
747            {
748                List<String> typeNames = new ArrayList<String>();
749    
750                for (Class c : ALLOWED)
751                {
752                    String name = c.getName();
753    
754                    if (name.startsWith("java.lang."))
755                        name = name.substring(10);
756    
757                    typeNames.add(name);
758                }
759    
760                Collections.sort(typeNames);
761    
762                StringBuilder joined = new StringBuilder();
763                String sep = "";
764    
765                for (String name : typeNames)
766                {
767                    joined.append(sep);
768                    joined.append(name);
769    
770                    sep = ", ";
771                }
772    
773                String message = String.format("JSONObject properties may be one of %s. Type %s is not allowed.",
774                        joined.toString(), actual.getName());
775    
776                throw new RuntimeException(message);
777            }
778    
779            if (value instanceof Double)
780            {
781                Double asDouble = (Double) value;
782    
783                if (asDouble.isInfinite() || asDouble.isNaN())
784                {
785                    throw new RuntimeException(
786                            "JSON does not allow non-finite numbers.");
787                }
788    
789                return;
790            }
791    
792            if (value instanceof Float)
793            {
794                Float asFloat = (Float) value;
795    
796                if (asFloat.isInfinite() || asFloat.isNaN())
797                {
798                    throw new RuntimeException(
799                            "JSON does not allow non-finite numbers.");
800                }
801    
802            }
803    
804        }
805    
806        /**
807         * Prints the JSONObject using the session.
808         *
809         * @since 5.2.0
810         */
811        void print(JSONPrintSession session)
812        {
813            session.printSymbol('{');
814    
815            session.indent();
816    
817            boolean comma = false;
818    
819            for (String key : keys())
820            {
821                if (comma)
822                    session.printSymbol(',');
823    
824                session.newline();
825    
826                session.printQuoted(key);
827    
828                session.printSymbol(':');
829    
830                printValue(session, properties.get(key));
831    
832                comma = true;
833            }
834    
835            session.outdent();
836    
837            if (comma)
838                session.newline();
839    
840            session.printSymbol('}');
841        }
842    
843        /**
844         * Prints a value (a JSONArray or JSONObject, or a value stored in an array or object) using
845         * the session.
846         *
847         * @since 5.2.0
848         */
849        static void printValue(JSONPrintSession session, Object value)
850        {
851            if (value instanceof JSONObject)
852            {
853                ((JSONObject) value).print(session);
854                return;
855            }
856    
857            if (value instanceof JSONArray)
858            {
859                ((JSONArray) value).print(session);
860                return;
861            }
862    
863            if (value instanceof JSONString)
864            {
865                String printValue = ((JSONString) value).toJSONString();
866    
867                session.print(printValue);
868    
869                return;
870            }
871    
872            if (value instanceof Number)
873            {
874                String printValue = numberToString((Number) value);
875                session.print(printValue);
876                return;
877            }
878    
879            if (value instanceof Boolean)
880            {
881                session.print(value.toString());
882    
883                return;
884            }
885    
886            // Otherwise it really should just be a string. Nothing else can go in.
887            session.printQuoted(value.toString());
888        }
889    
890        /**
891         * Returns true if the other object is a JSONObject and its set of properties matches this object's properties.
892         */
893        @Override
894        public boolean equals(Object obj)
895        {
896            if (obj == null)
897                return false;
898    
899            if (!(obj instanceof JSONObject))
900                return false;
901    
902            JSONObject other = (JSONObject) obj;
903    
904            return properties.equals(other.properties);
905        }
906    }