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