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