X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Forg%2Fjson%2FJSONObject.java;fp=src%2Forg%2Fjson%2FJSONObject.java;h=4b5c5ea05111b7e0bf14591b66d37aaaf8a20f52;hb=3459a8a691cb22508d7067f240b7254e588e77d3;hp=42d34ded6f9db0a18bcdb7b1e1e45ed9e22fc3c2;hpb=5b27f1062b2203c4c31702e205f4c78e1992063e;p=jalview.git diff --git a/src/org/json/JSONObject.java b/src/org/json/JSONObject.java index 42d34de..4b5c5ea 100644 --- a/src/org/json/JSONObject.java +++ b/src/org/json/JSONObject.java @@ -99,2313 +99,2910 @@ import java.util.Set; * @author JSON.org * @version 2016-08-15 */ -public class JSONObject { - /** - * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst - * Java's null is equivalent to the value that JavaScript calls undefined. - */ - private static final class Null { - - /** - * There is only intended to be a single instance of the NULL object, so the - * clone method returns itself. - * - * @return NULL. - */ - @Override - protected final Object clone() { - return this; - } - - /** - * A Null object is equal to the null value and to itself. - * - * @param object An object to test for nullness. - * @return true if the object parameter is the JSONObject.NULL object or null. - */ - @Override - public boolean equals(Object object) { - return object == null || object == this; - } - - /** - * A Null object is equal to the null value and to itself. - * - * @return always returns 0. - */ - @Override - public int hashCode() { - return 0; - } - - /** - * Get the "null" string value. - * - * @return The string "null". - */ - @Override - public String toString() { - return "null"; - } - } - - /** - * The map where the JSONObject's properties are kept. - */ - private final Map map; - - /** - * It is sometimes more convenient and less ambiguous to have a - * NULL object than to use Java's null value. - * JSONObject.NULL.equals(null) returns true. - * JSONObject.NULL.toString() returns "null". - */ - public static final Object NULL = new Null(); - - /** - * Construct an empty JSONObject. - */ - public JSONObject() { - // HashMap is used on purpose to ensure that elements are unordered by - // the specification. - // JSON tends to be a portable transfer format to allows the container - // implementations to rearrange their items for a faster element - // retrieval based on associative access. - // Therefore, an implementation mustn't rely on the order of the item. - this.map = new HashMap(); - } - - /** - * Construct a JSONObject from a subset of another JSONObject. An array of - * strings is used to identify the keys that should be copied. Missing keys are - * ignored. - * - * @param jo A JSONObject. - * @param names An array of strings. - */ - public JSONObject(JSONObject jo, String[] names) { - this(names.length); - for (int i = 0; i < names.length; i += 1) { - try { - this.putOnce(names[i], jo.opt(names[i])); - } catch (Exception ignore) { - } - } - } - - /** - * Construct a JSONObject from a JSONTokener. - * - * @param x A JSONTokener object containing the source string. - * @throws JSONException If there is a syntax error in the source string or a - * duplicated key. - */ - public JSONObject(JSONTokener x) throws JSONException { - this(); - char c; - String key; - - if (x.nextClean() != '{') { - throw x.syntaxError("A JSONObject text must begin with '{'"); - } - for (;;) { - c = x.nextClean(); - switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); - } - - // The key is followed by ':'. - - c = x.nextClean(); - if (c != ':') { - throw x.syntaxError("Expected a ':' after a key"); - } - - // Use syntaxError(..) to include error location - - if (key != null) { - // Check if key exists - if (this.opt(key) != null) { - // key already exists - throw x.syntaxError("Duplicate key \"" + key + "\""); - } - // Only add value if non-null - Object value = x.nextValue(); - if (value != null) { - this.put(key, value); - } - } - - // Pairs are separated by ','. - - switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { - return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); - } - } - } - - /** - * Construct a JSONObject from a Map. - * - * @param m A map object that can be used to initialize the contents of the - * JSONObject. - * @throws JSONException If a value in the map is non-finite number. - * @throws NullPointerException If a key in the map is null - */ - public JSONObject(Map m) { - if (m == null) { - this.map = new HashMap(); - } else { - this.map = new HashMap(m.size()); - for (final Entry e : m.entrySet()) { - if (e.getKey() == null) { - throw new NullPointerException("Null key."); - } - final Object value = e.getValue(); - if (value != null) { - this.map.put(String.valueOf(e.getKey()), wrap(value)); - } - } - } - } - - /** - * Construct a JSONObject from an Object using bean getters. It reflects on all - * of the public methods of the object. For each of the methods with no - * parameters and a name starting with "get" or "is" - * followed by an uppercase letter, the method is invoked, and a key and the - * value returned from the getter method are put into the new JSONObject. - *

- * The key is formed by removing the "get" or "is" - * prefix. If the second remaining character is not upper case, then the first - * character is converted to lower case. - *

- * Methods that are static, return void, have - * parameters, or are "bridge" methods, are ignored. - *

- * For example, if an object has a method named "getName", and if - * the result of calling object.getName() is - * "Larry Fine", then the JSONObject will contain - * "name": "Larry Fine". - *

- * The {@link JSONPropertyName} annotation can be used on a bean getter to - * override key name used in the JSONObject. For example, using the object above - * with the getName method, if we annotated it with: - * - *

-	 * @JSONPropertyName("FullName")
-	 * public String getName() {
-	 * 	return this.name;
-	 * }
-	 * 
- * - * The resulting JSON object would contain "FullName": "Larry Fine" - *

- * Similarly, the {@link JSONPropertyName} annotation can be used on non- - * get and is methods. We can also override key name - * used in the JSONObject as seen below even though the field would normally be - * ignored: - * - *

-	 * @JSONPropertyName("FullName")
-	 * public String fullName() {
-	 * 	return this.name;
-	 * }
-	 * 
- * - * The resulting JSON object would contain "FullName": "Larry Fine" - *

- * The {@link JSONPropertyIgnore} annotation can be used to force the bean - * property to not be serialized into JSON. If both {@link JSONPropertyIgnore} - * and {@link JSONPropertyName} are defined on the same method, a depth - * comparison is performed and the one closest to the concrete class being - * serialized is used. If both annotations are at the same level, then the - * {@link JSONPropertyIgnore} annotation takes precedent and the field is not - * serialized. For example, the following declaration would prevent the - * getName method from being serialized: - * - *

-	 * @JSONPropertyName("FullName")
-	 * @JSONPropertyIgnore
-	 * public String getName() {
-	 * 	return this.name;
-	 * }
-	 * 
- *

- * - * @param bean An object that has getter methods that should be used to make a - * JSONObject. - */ - public JSONObject(Object bean) { - this(); - this.populateMap(bean); - } - - /** - * Construct a JSONObject from an Object, using reflection to find the public - * members. The resulting JSONObject's keys will be the strings from the names - * array, and the values will be the field values associated with those keys in - * the object. If a key is not found or not visible, then it will not be copied - * into the new JSONObject. - * - * @param object An object that has fields that should be used to make a - * JSONObject. - * @param names An array of strings, the names of the fields to be obtained - * from the object. - */ - public JSONObject(Object object, String names[]) { - this(names.length); - Class c = object.getClass(); - for (int i = 0; i < names.length; i += 1) { - String name = names[i]; - try { - this.putOpt(name, c.getField(name).get(object)); - } catch (Exception ignore) { - } - } - } - - /** - * Construct a JSONObject from a source JSON text string. This is the most - * commonly used JSONObject constructor. - * - * @param source A string beginning with { (left - * brace) and ending with } - *  (right brace). - * @exception JSONException If there is a syntax error in the source string or a - * duplicated key. - */ - public JSONObject(String source) throws JSONException { - this(new JSONTokener(source)); - } - - /** - * Construct a JSONObject from a ResourceBundle. - * - * @param baseName The ResourceBundle base name. - * @param locale The Locale to load the ResourceBundle for. - * @throws JSONException If any JSONExceptions are detected. - */ - public JSONObject(String baseName, Locale locale) throws JSONException { - this(); - ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, - Thread.currentThread().getContextClassLoader()); - -// Iterate through the keys in the bundle. - - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - Object key = keys.nextElement(); - if (key != null) { - -// Go through the path, ensuring that there is a nested JSONObject for each -// segment except the last. Add the value using the last segment's name into -// the deepest nested JSONObject. - - String[] path = ((String) key).split("\\."); - int last = path.length - 1; - JSONObject target = this; - for (int i = 0; i < last; i += 1) { - String segment = path[i]; - JSONObject nextTarget = target.optJSONObject(segment); - if (nextTarget == null) { - nextTarget = new JSONObject(); - target.put(segment, nextTarget); - } - target = nextTarget; - } - target.put(path[last], bundle.getString((String) key)); - } - } - } - - /** - * Constructor to specify an initial capacity of the internal map. Useful for - * library internal calls where we know, or at least can best guess, how big - * this JSONObject will be. - * - * @param initialCapacity initial capacity of the internal map. - */ - protected JSONObject(int initialCapacity) { - this.map = new HashMap(initialCapacity); - } - - /** - * Accumulate values under a key. It is similar to the put method except that if - * there is already an object stored under the key then a JSONArray is stored - * under the key to hold all of the accumulated values. If there is already a - * JSONArray, then the new value is appended to it. In contrast, the put method - * replaces the previous value. - * - * If only one value is accumulated that is not a JSONArray, then the result - * will be the same as using put. But if multiple values are accumulated, then - * the result will be like append. - * - * @param key A key string. - * @param value An object to be accumulated under the key. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject accumulate(String key, Object value) throws JSONException { - testValidity(value); - Object object = this.opt(key); - if (object == null) { - this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); - } else if (object instanceof JSONArray) { - ((JSONArray) object).put(value); - } else { - this.put(key, new JSONArray().put(object).put(value)); - } - return this; - } - - /** - * Append values to the array under a key. If the key does not exist in the - * JSONObject, then the key is put in the JSONObject with its value being a - * JSONArray containing the value parameter. If the key was already associated - * with a JSONArray, then the value parameter is appended to it. - * - * @param key A key string. - * @param value An object to be accumulated under the key. - * @return this. - * @throws JSONException If the value is non-finite number or if the - * current value associated with the key is not a - * JSONArray. - * @throws NullPointerException If the key is null. - */ - public JSONObject append(String key, Object value) throws JSONException { - testValidity(value); - Object object = this.opt(key); - if (object == null) { - this.put(key, new JSONArray().put(value)); - } else if (object instanceof JSONArray) { - this.put(key, ((JSONArray) object).put(value)); - } else { - throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); - } - return this; - } - - /** - * Produce a string from a double. The string "null" will be returned if the - * number is not finite. - * - * @param d A double. - * @return A String. - */ - public static String doubleToString(double d) { - if (Double.isInfinite(d) || Double.isNaN(d)) { - return "null"; - } - -// Shave off trailing zeros and decimal point, if possible. - - String string = Double.toString(d); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Get the value object associated with a key. - * - * @param key A key string. - * @return The object associated with the key. - * @throws JSONException if the key is not found. - */ - public Object get(String key) throws JSONException { - if (key == null) { - throw new JSONException("Null key."); - } - Object object = this.opt(key); - if (object == null) { - throw new JSONException("JSONObject[" + quote(key) + "] not found."); - } - return object; - } - - /** - * Get the enum value associated with a key. - * - * @param clazz The type of enum to retrieve. - * @param key A key string. - * @return The enum value associated with the key - * @throws JSONException if the key is not found or if the value cannot be - * converted to an enum. - */ - public > E getEnum(Class clazz, String key) throws JSONException { - E val = optEnum(clazz, key); - if (val == null) { - // JSONException should really take a throwable argument. - // If it did, I would re-implement this with the Enum.valueOf - // method and place any thrown exception in the JSONException - throw new JSONException( - "JSONObject[" + quote(key) + "] is not an enum of type " + quote(clazz.getSimpleName()) + "."); - } - return val; - } - - /** - * Get the boolean value associated with a key. - * - * @param key A key string. - * @return The truth. - * @throws JSONException if the value is not a Boolean or the String "true" or - * "false". - */ - public boolean getBoolean(String key) throws JSONException { - Object object = this.get(key); - if (object.equals(Boolean.FALSE) || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) { - return false; - } else if (object.equals(Boolean.TRUE) - || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) { - return true; - } - throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); - } - - /** - * Get the BigInteger value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or if the value cannot be - * converted to BigInteger. - */ - public BigInteger getBigInteger(String key) throws JSONException { - Object object = this.get(key); - try { - return new BigInteger(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigInteger.", e); - } - } - - /** - * Get the BigDecimal value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or if the value cannot be - * converted to BigDecimal. - */ - public BigDecimal getBigDecimal(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof BigDecimal) { - return (BigDecimal) object; - } - try { - return new BigDecimal(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigDecimal.", e); - } - } - - /** - * Get the double value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public double getDouble(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).doubleValue() : Double.parseDouble(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); - } - } - - /** - * Get the float value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public float getFloat(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).floatValue() : Float.parseFloat(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); - } - } - - /** - * Get the Number value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or if the value is not a Number - * object and cannot be converted to a number. - */ - public Number getNumber(String key) throws JSONException { - Object object = this.get(key); - try { - if (object instanceof Number) { - return (Number) object; - } - return stringToNumber(object.toString()); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e); - } - } - - /** - * Get the int value associated with a key. - * - * @param key A key string. - * @return The integer value. - * @throws JSONException if the key is not found or if the value cannot be - * converted to an integer. - */ - public int getInt(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).intValue() : Integer.parseInt((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] is not an int.", e); - } - } - - /** - * Get the JSONArray value associated with a key. - * - * @param key A key string. - * @return A JSONArray which is the value. - * @throws JSONException if the key is not found or if the value is not a - * JSONArray. - */ - public JSONArray getJSONArray(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof JSONArray) { - return (JSONArray) object; - } - throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); - } - - /** - * Get the JSONObject value associated with a key. - * - * @param key A key string. - * @return A JSONObject which is the value. - * @throws JSONException if the key is not found or if the value is not a - * JSONObject. - */ - public JSONObject getJSONObject(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof JSONObject) { - return (JSONObject) object; - } - throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); - } - - /** - * Get the long value associated with a key. - * - * @param key A key string. - * @return The long value. - * @throws JSONException if the key is not found or if the value cannot be - * converted to a long. - */ - public long getLong(String key) throws JSONException { - Object object = this.get(key); - try { - return object instanceof Number ? ((Number) object).longValue() : Long.parseLong((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + "] is not a long.", e); - } - } - - /** - * Get an array of field names from a JSONObject. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(JSONObject jo) { - if (jo.isEmpty()) { - return null; - } - return jo.keySet().toArray(new String[jo.length()]); - } - - /** - * Get an array of field names from an Object. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(Object object) { - if (object == null) { - return null; - } - Class klass = object.getClass(); - Field[] fields = klass.getFields(); - int length = fields.length; - if (length == 0) { - return null; - } - String[] names = new String[length]; - for (int i = 0; i < length; i += 1) { - names[i] = fields[i].getName(); - } - return names; - } - - /** - * Get the string associated with a key. - * - * @param key A key string. - * @return A string which is the value. - * @throws JSONException if there is no string value for the key. - */ - public String getString(String key) throws JSONException { - Object object = this.get(key); - if (object instanceof String) { - return (String) object; - } - throw new JSONException("JSONObject[" + quote(key) + "] not a string."); - } - - /** - * Determine if the JSONObject contains a specific key. - * - * @param key A key string. - * @return true if the key exists in the JSONObject. - */ - public boolean has(String key) { - return this.map.containsKey(key); - } - - /** - * Increment a property of a JSONObject. If there is no such property, create - * one with a value of 1. If there is such a property, and if it is an Integer, - * Long, Double, or Float, then add one to it. - * - * @param key A key string. - * @return this. - * @throws JSONException If there is already a property with this name that is - * not an Integer, Long, Double, or Float. - */ - public JSONObject increment(String key) throws JSONException { - Object value = this.opt(key); - if (value == null) { - this.put(key, 1); - } else if (value instanceof BigInteger) { - this.put(key, ((BigInteger) value).add(BigInteger.ONE)); - } else if (value instanceof BigDecimal) { - this.put(key, ((BigDecimal) value).add(BigDecimal.ONE)); - } else if (value instanceof Integer) { - this.put(key, ((Integer) value).intValue() + 1); - } else if (value instanceof Long) { - this.put(key, ((Long) value).longValue() + 1L); - } else if (value instanceof Double) { - this.put(key, ((Double) value).doubleValue() + 1.0d); - } else if (value instanceof Float) { - this.put(key, ((Float) value).floatValue() + 1.0f); - } else { - throw new JSONException("Unable to increment [" + quote(key) + "]."); - } - return this; - } - - /** - * Determine if the value associated with the key is null or if - * there is no value. - * - * @param key A key string. - * @return true if there is no value associated with the key or if the value is - * the JSONObject.NULL object. - */ - public boolean isNull(String key) { - return JSONObject.NULL.equals(this.opt(key)); - } - - /** - * Get an enumeration of the keys of the JSONObject. Modifying this key Set will - * also modify the JSONObject. Use with caution. - * - * @see Set#iterator() - * - * @return An iterator of the keys. - */ - public Iterator keys() { - return this.keySet().iterator(); - } - - /** - * Get a set of keys of the JSONObject. Modifying this key Set will also modify - * the JSONObject. Use with caution. - * - * @see Map#keySet() - * - * @return A keySet. - */ - public Set keySet() { - return this.map.keySet(); - } - - /** - * Get a set of entries of the JSONObject. These are raw values and may not - * match what is returned by the JSONObject get* and opt* functions. Modifying - * the returned EntrySet or the Entry objects contained therein will modify the - * backing JSONObject. This does not return a clone or a read-only view. - * - * Use with caution. - * - * @see Map#entrySet() - * - * @return An Entry Set - */ - protected Set> entrySet() { - return this.map.entrySet(); - } - - /** - * Get the number of keys stored in the JSONObject. - * - * @return The number of keys in the JSONObject. - */ - public int length() { - return this.map.size(); - } - - /** - * Check if JSONObject is empty. - * - * @return true if JSONObject is empty, otherwise false. - */ - public boolean isEmpty() { - return map.isEmpty(); - } - - /** - * Produce a JSONArray containing the names of the elements of this JSONObject. - * - * @return A JSONArray containing the key strings, or null if the JSONObject is - * empty. - */ - public JSONArray names() { - if (this.map.isEmpty()) { - return null; - } - return new JSONArray(this.map.keySet()); - } - - /** - * Produce a string from a Number. - * - * @param number A Number - * @return A String. - * @throws JSONException If n is a non-finite number. - */ - public static String numberToString(Number number) throws JSONException { - if (number == null) { - throw new JSONException("Null pointer"); - } - testValidity(number); - - // Shave off trailing zeros and decimal point, if possible. - - String string = number.toString(); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Get an optional value associated with a key. - * - * @param key A key string. - * @return An object which is the value, or null if there is no value. - */ - public Object opt(String key) { - return key == null ? null : this.map.get(key); - } - - /** - * Get the enum value associated with a key. - * - * @param clazz The type of enum to retrieve. - * @param key A key string. - * @return The enum value associated with the key or null if not found - */ - public > E optEnum(Class clazz, String key) { - return this.optEnum(clazz, key, null); - } - - /** - * Get the enum value associated with a key. - * - * @param clazz The type of enum to retrieve. - * @param key A key string. - * @param defaultValue The default in case the value is not found - * @return The enum value associated with the key or defaultValue if the value - * is not found or cannot be assigned to clazz - */ - public > E optEnum(Class clazz, String key, E defaultValue) { - try { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (clazz.isAssignableFrom(val.getClass())) { - // we just checked it! - @SuppressWarnings("unchecked") - E myE = (E) val; - return myE; - } - return Enum.valueOf(clazz, val.toString()); - } catch (IllegalArgumentException e) { - return defaultValue; - } catch (NullPointerException e) { - return defaultValue; - } - } - - /** - * Get an optional boolean associated with a key. It returns false if there is - * no such key, or if the value is not Boolean.TRUE or the String "true". - * - * @param key A key string. - * @return The truth. - */ - public boolean optBoolean(String key) { - return this.optBoolean(key, false); - } - - /** - * Get an optional boolean associated with a key. It returns the defaultValue if - * there is no such key, or if it is not a Boolean or the String "true" or - * "false" (case insensitive). - * - * @param key A key string. - * @param defaultValue The default. - * @return The truth. - */ - public boolean optBoolean(String key, boolean defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Boolean) { - return ((Boolean) val).booleanValue(); - } - try { - // we'll use the get anyway because it does string conversion. - return this.getBoolean(key); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional BigDecimal associated with a key, or the defaultValue if - * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigDecimal) { - return (BigDecimal) val; - } - if (val instanceof BigInteger) { - return new BigDecimal((BigInteger) val); - } - if (val instanceof Double || val instanceof Float) { - return new BigDecimal(((Number) val).doubleValue()); - } - if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte) { - return new BigDecimal(((Number) val).longValue()); - } - // don't check if it's a string in case of unchecked Number subclasses - try { - return new BigDecimal(val.toString()); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional BigInteger associated with a key, or the defaultValue if - * there is no such key or if its value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public BigInteger optBigInteger(String key, BigInteger defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof BigInteger) { - return (BigInteger) val; - } - if (val instanceof BigDecimal) { - return ((BigDecimal) val).toBigInteger(); - } - if (val instanceof Double || val instanceof Float) { - return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); - } - if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte) { - return BigInteger.valueOf(((Number) val).longValue()); - } - // don't check if it's a string in case of unchecked Number subclasses - try { - // the other opt functions handle implicit conversions, i.e. - // jo.put("double",1.1d); - // jo.optInt("double"); -- will return 1, not an error - // this conversion to BigDecimal then to BigInteger is to maintain - // that type cast support that may truncate the decimal. - final String valStr = val.toString(); - if (isDecimalNotation(valStr)) { - return new BigDecimal(valStr).toBigInteger(); - } - return new BigInteger(valStr); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional double associated with a key, or NaN if there is no such key - * or if its value is not a number. If the value is a string, an attempt will be - * made to evaluate it as a number. - * - * @param key A string which is the key. - * @return An object which is the value. - */ - public double optDouble(String key) { - return this.optDouble(key, Double.NaN); - } - - /** - * Get an optional double associated with a key, or the defaultValue if there is - * no such key or if its value is not a number. If the value is a string, an - * attempt will be made to evaluate it as a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public double optDouble(String key, double defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number) { - return ((Number) val).doubleValue(); - } - if (val instanceof String) { - try { - return Double.parseDouble((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get the optional double value associated with an index. NaN is returned if - * there is no value for the index, or if the value is not a number and cannot - * be converted to a number. - * - * @param key A key string. - * @return The value. - */ - public float optFloat(String key) { - return this.optFloat(key, Float.NaN); - } - - /** - * Get the optional double value associated with an index. The defaultValue is - * returned if there is no value for the index, or if the value is not a number - * and cannot be converted to a number. - * - * @param key A key string. - * @param defaultValue The default value. - * @return The value. - */ - public float optFloat(String key, float defaultValue) { - Object val = this.opt(key); - if (JSONObject.NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number) { - return ((Number) val).floatValue(); - } - if (val instanceof String) { - try { - return Float.parseFloat((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional int value associated with a key, or zero if there is no such - * key or if the value is not a number. If the value is a string, an attempt - * will be made to evaluate it as a number. - * - * @param key A key string. - * @return An object which is the value. - */ - public int optInt(String key) { - return this.optInt(key, 0); - } - - /** - * Get an optional int value associated with a key, or the default if there is - * no such key or if the value is not a number. If the value is a string, an - * attempt will be made to evaluate it as a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public int optInt(String key, int defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number) { - return ((Number) val).intValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal((String) val).intValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional JSONArray associated with a key. It returns null if there is - * no such key, or if its value is not a JSONArray. - * - * @param key A key string. - * @return A JSONArray which is the value. - */ - public JSONArray optJSONArray(String key) { - Object o = this.opt(key); - return o instanceof JSONArray ? (JSONArray) o : null; - } - - /** - * Get an optional JSONObject associated with a key. It returns null if there is - * no such key, or if its value is not a JSONObject. - * - * @param key A key string. - * @return A JSONObject which is the value. - */ - public JSONObject optJSONObject(String key) { - Object object = this.opt(key); - return object instanceof JSONObject ? (JSONObject) object : null; - } - - /** - * Get an optional long value associated with a key, or zero if there is no such - * key or if the value is not a number. If the value is a string, an attempt - * will be made to evaluate it as a number. - * - * @param key A key string. - * @return An object which is the value. - */ - public long optLong(String key) { - return this.optLong(key, 0); - } - - /** - * Get an optional long value associated with a key, or the default if there is - * no such key or if the value is not a number. If the value is a string, an - * attempt will be made to evaluate it as a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public long optLong(String key, long defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number) { - return ((Number) val).longValue(); - } - - if (val instanceof String) { - try { - return new BigDecimal((String) val).longValue(); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional {@link Number} value associated with a key, or - * null if there is no such key or if the value is not a number. If - * the value is a string, an attempt will be made to evaluate it as a number - * ({@link BigDecimal}). This method would be used in cases where type coercion - * of the number value is unwanted. - * - * @param key A key string. - * @return An object which is the value. - */ - public Number optNumber(String key) { - return this.optNumber(key, null); - } - - /** - * Get an optional {@link Number} value associated with a key, or the default if - * there is no such key or if the value is not a number. If the value is a - * string, an attempt will be made to evaluate it as a number. This method would - * be used in cases where type coercion of the number value is unwanted. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public Number optNumber(String key, Number defaultValue) { - Object val = this.opt(key); - if (NULL.equals(val)) { - return defaultValue; - } - if (val instanceof Number) { - return (Number) val; - } - - if (val instanceof String) { - try { - return stringToNumber((String) val); - } catch (Exception e) { - return defaultValue; - } - } - return defaultValue; - } - - /** - * Get an optional string associated with a key. It returns an empty string if - * there is no such key. If the value is not a string and is not null, then it - * is converted to a string. - * - * @param key A key string. - * @return A string which is the value. - */ - public String optString(String key) { - return this.optString(key, ""); - } - - /** - * Get an optional string associated with a key. It returns the defaultValue if - * there is no such key. - * - * @param key A key string. - * @param defaultValue The default. - * @return A string which is the value. - */ - public String optString(String key, String defaultValue) { - Object object = this.opt(key); - return NULL.equals(object) ? defaultValue : object.toString(); - } - - /** - * Populates the internal map of the JSONObject with the bean properties. The - * bean can not be recursive. - * - * @see JSONObject#JSONObject(Object) - * - * @param bean the bean - */ - private void populateMap(Object bean) { - Class klass = bean.getClass(); - - // If klass is a System class then set includeSuperClass to false. - - boolean includeSuperClass = klass.getClassLoader() != null; - - Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); - for (final Method method : methods) { - final int modifiers = method.getModifiers(); - if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && method.getParameterTypes().length == 0 - && !method.isBridge() && method.getReturnType() != Void.TYPE - && isValidMethodName(method.getName())) { - final String key = getKeyNameFromMethod(method); - if (key != null && !key.isEmpty()) { - try { - final Object result = method.invoke(bean); - if (result != null) { - this.map.put(key, wrap(result)); - // we don't use the result anywhere outside of wrap - // if it's a resource we should be sure to close it - // after calling toString - if (result instanceof Closeable) { - try { - ((Closeable) result).close(); - } catch (IOException ignore) { - } - } - } - } catch (IllegalAccessException ignore) { - } catch (IllegalArgumentException ignore) { - } catch (InvocationTargetException ignore) { - } - } - } - } - } - - private boolean isValidMethodName(String name) { - return !"getClass".equals(name) && !"getDeclaringClass".equals(name); - } - - private String getKeyNameFromMethod(Method method) { - final int ignoreDepth = -1;// getAnnotationDepth(method, JSONPropertyIgnore.class); -// if (ignoreDepth > 0) { -// final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class); -// if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { -// // the hierarchy asked to ignore, and the nearest name override -// // was higher or non-existent -// return null; -// } -// } -// JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); -// if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) { -// return annotation.value(); -// } - String key; - final String name = method.getName(); - if (name.startsWith("get") && name.length() > 3) { - key = name.substring(3); - } else if (name.startsWith("is") && name.length() > 2) { - key = name.substring(2); - } else { - return null; - } - // if the first letter in the key is not uppercase, then skip. - // This is to maintain backwards compatibility before PR406 - // (https://github.com/stleary/JSON-java/pull/406/) - if (Character.isLowerCase(key.charAt(0))) { - return null; - } - if (key.length() == 1) { - key = key.toLowerCase(Locale.ROOT); - } else if (!Character.isUpperCase(key.charAt(1))) { - key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); - } - return (/** @j2sNative 1 ? key.split("$")[0] : */ - key); - } - -// /** -// * Searches the class hierarchy to see if the method or it's super -// * implementations and interfaces has the annotation. -// * -// * @param -// * type of the annotation -// * -// * @param m -// * method to check -// * @param annotationClass -// * annotation to look for -// * @return the {@link Annotation} if the annotation exists on the current method -// * or one of it's super class definitions -// */ -// private static A getAnnotation(final Method m, final Class annotationClass) { -// return null; -// // if we have invalid data the result is null -// if (true || m == null || annotationClass == null) { -// return null; -// } -// -// if (m.isAnnotationPresent(annotationClass)) { -// return m.getAnnotation(annotationClass); -// } -// -// // if we've already reached the Object class, return null; -// Class c = m.getDeclaringClass(); -// if (c.getSuperclass() == null) { -// return null; -// } -// -// // check directly implemented interfaces for the method being checked -// for (Class i : c.getInterfaces()) { -// try { -// Method im = i.getMethod(m.getName(), m.getParameterTypes()); -// return getAnnotation(im, annotationClass); -// } catch (final SecurityException ex) { -// continue; -// } catch (final NoSuchMethodException ex) { -// continue; -// } -// } -// -// try { -// return getAnnotation( -// c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), -// annotationClass); -// } catch (final SecurityException ex) { -// return null; -// } catch (final NoSuchMethodException ex) { -// return null; -// } -// } -// -// /** -// * Searches the class hierarchy to see if the method or it's super -// * implementations and interfaces has the annotation. Returns the depth of the -// * annotation in the hierarchy. -// * -// * @param -// * type of the annotation -// * -// * @param m -// * method to check -// * @param annotationClass -// * annotation to look for -// * @return Depth of the annotation or -1 if the annotation is not on the method. -// */ -// private static int getAnnotationDepth(final Method m, final Class annotationClass) { -// // if we have invalid data the result is -1 -// if (m == null || annotationClass == null) { -// return -1; -// } -// if (m.isAnnotationPresent(annotationClass)) { -// return 1; -// } -// -// // if we've already reached the Object class, return -1; -// Class c = m.getDeclaringClass(); -// if (c.getSuperclass() == null) { -// return -1; -// } -// -// // check directly implemented interfaces for the method being checked -// for (Class i : c.getInterfaces()) { -// try { -// Method im = i.getMethod(m.getName(), m.getParameterTypes()); -// int d = getAnnotationDepth(im, annotationClass); -// if (d > 0) { -// // since the annotation was on the interface, add 1 -// return d + 1; -// } -// } catch (final SecurityException ex) { -// continue; -// } catch (final NoSuchMethodException ex) { -// continue; -// } -// } -// -// try { -// int d = getAnnotationDepth( -// c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), -// annotationClass); -// if (d > 0) { -// // since the annotation was on the superclass, add 1 -// return d + 1; -// } -// return -1; -// } catch (final SecurityException ex) { -// return -1; -// } catch (final NoSuchMethodException ex) { -// return -1; -// } -// } - - /** - * Put a key/boolean pair in the JSONObject. - * - * @param key A key string. - * @param value A boolean which is the value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, boolean value) throws JSONException { - return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a JSONArray - * which is produced from a Collection. - * - * @param key A key string. - * @param value A Collection value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, Collection value) throws JSONException { - return this.put(key, new JSONArray(value)); - } - - /** - * Put a key/double pair in the JSONObject. - * - * @param key A key string. - * @param value A double which is the value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, double value) throws JSONException { - return this.put(key, Double.valueOf(value)); - } - - /** - * Put a key/float pair in the JSONObject. - * - * @param key A key string. - * @param value A float which is the value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, float value) throws JSONException { - return this.put(key, Float.valueOf(value)); - } - - /** - * Put a key/int pair in the JSONObject. - * - * @param key A key string. - * @param value An int which is the value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, int value) throws JSONException { - return this.put(key, Integer.valueOf(value)); - } - - /** - * Put a key/long pair in the JSONObject. - * - * @param key A key string. - * @param value A long which is the value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, long value) throws JSONException { - return this.put(key, Long.valueOf(value)); - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a JSONObject - * which is produced from a Map. - * - * @param key A key string. - * @param value A Map value. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, Map value) throws JSONException { - return this.put(key, new JSONObject(value)); - } - - /** - * Put a key/value pair in the JSONObject. If the value is null, - * then the key will be removed from the JSONObject if it is present. - * - * @param key A key string. - * @param value An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, - * String, or the JSONObject.NULL object. - * @return this. - * @throws JSONException If the value is non-finite number. - * @throws NullPointerException If the key is null. - */ - public JSONObject put(String key, Object value) throws JSONException { - if (key == null) { - throw new NullPointerException("Null key."); - } - if (value != null) { - testValidity(value); - this.map.put(key, value); - } else { - this.remove(key); - } - return this; - } - - /** - * Put a key/value pair in the JSONObject, but only if the key and the value are - * both non-null, and only if there is not already a member with that name. - * - * @param key string - * @param value object - * @return this. - * @throws JSONException if the key is a duplicate - */ - public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null && value != null) { - if (this.opt(key) != null) { - throw new JSONException("Duplicate key \"" + key + "\""); - } - return this.put(key, value); - } - return this; - } - - /** - * Put a key/value pair in the JSONObject, but only if the key and the value are - * both non-null. - * - * @param key A key string. - * @param value An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, - * String, or the JSONObject.NULL object. - * @return this. - * @throws JSONException If the value is a non-finite number. - */ - public JSONObject putOpt(String key, Object value) throws JSONException { - if (key != null && value != null) { - return this.put(key, value); - } - return this; - } - - /** - * Creates a JSONPointer using an initialization string and tries to match it to - * an item within this JSONObject. For example, given a JSONObject initialized - * with this document: - * - *

-	 * {
-	 *     "a":{"b":"c"}
-	 * }
-	 * 
- * - * and this JSONPointer string: - * - *
-	 * "/a/b"
-	 * 
- * - * Then this method will return the String "c". A JSONPointerException may be - * thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(String jsonPointer) { - return query(new JSONPointer(jsonPointer)); - } - - /** - * Uses a user initialized JSONPointer and tries to match it to an item within - * this JSONObject. For example, given a JSONObject initialized with this - * document: - * - *
-	 * {
-	 *     "a":{"b":"c"}
-	 * }
-	 * 
- * - * and this JSONPointer: - * - *
-	 * "/a/b"
-	 * 
- * - * Then this method will return the String "c". A JSONPointerException may be - * thrown from code called by this method. - * - * @param jsonPointer string that can be used to create a JSONPointer - * @return the item matched by the JSONPointer, otherwise null - */ - public Object query(JSONPointer jsonPointer) { - return jsonPointer.queryFrom(this); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer the string representation of the JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(String jsonPointer) { - return optQuery(new JSONPointer(jsonPointer)); - } - - /** - * Queries and returns a value from this object using {@code jsonPointer}, or - * returns null if the query fails due to a missing key. - * - * @param jsonPointer The JSON pointer - * @return the queried value or {@code null} - * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax - */ - public Object optQuery(JSONPointer jsonPointer) { - try { - return jsonPointer.queryFrom(this); - } catch (JSONPointerException e) { - return null; - } - } - - /** - * Produce a string in double quotes with backslash sequences in all the right - * places. A backslash will be inserted within = '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) { - w.write("\\u"); - hhhh = Integer.toHexString(c); - w.write("0000", 0, 4 - hhhh.length()); - w.write(hhhh); - } else { - w.write(c); - } - } - } - w.write('"'); - return w; - } - - /** - * Remove a name and its value, if present. - * - * @param key The name to be removed. - * @return The value that was associated with the name, or null if there was no - * value. - */ - public Object remove(String key) { - return this.map.remove(key); - } - - /** - * Determine if two JSONObjects are similar. They must contain the same set of - * names which must be associated with similar values. - * - * @param other The other JSONObject - * @return true if they are equal - */ - public boolean similar(Object other) { - try { - if (!(other instanceof JSONObject)) { - return false; - } - if (!this.keySet().equals(((JSONObject) other).keySet())) { - return false; - } - for (final Entry entry : this.entrySet()) { - String name = entry.getKey(); - Object valueThis = entry.getValue(); - Object valueOther = ((JSONObject) other).get(name); - if (valueThis == valueOther) { - continue; - } - if (valueThis == null) { - return false; - } - if (valueThis instanceof JSONObject) { - if (!((JSONObject) valueThis).similar(valueOther)) { - return false; - } - } else if (valueThis instanceof JSONArray) { - if (!((JSONArray) valueThis).similar(valueOther)) { - return false; - } - } else if (!valueThis.equals(valueOther)) { - return false; - } - } - return true; - } catch (Throwable exception) { - return false; - } - } - - /** - * Tests if the value should be tried as a decimal. It makes no test if there - * are actual digits. - * - * @param val value to test - * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false - * otherwise. - */ - protected static boolean isDecimalNotation(final String val) { - return val.indexOf('.') > -1 || val.indexOf('e') > -1 || val.indexOf('E') > -1 || "-0".equals(val); - } - - /** - * Converts a string to a number using the narrowest possible type. Possible - * returns for this function are BigDecimal, Double, BigInteger, Long, and - * Integer. When a Double is returned, it should always be a valid Double and - * not NaN or +-infinity. - * - * @param val value to convert - * @return Number representation of the value. - * @throws NumberFormatException thrown if the value is not a valid number. A - * public caller should catch this and wrap it in - * a {@link JSONException} if applicable. - */ - protected static Number stringToNumber(final String val) throws NumberFormatException { - char initial = val.charAt(0); - if ((initial >= '0' && initial <= '9') || initial == '-') { - // decimal representation - if (isDecimalNotation(val)) { - // quick dirty way to see if we need a BigDecimal instead of a Double - // this only handles some cases of overflow or underflow - if (val.length() > 14) { - return new BigDecimal(val); - } - final Double d = Double.valueOf(val); - if (d.isInfinite() || d.isNaN()) { - // if we can't parse it as a double, go up to BigDecimal - // this is probably due to underflow like 4.32e-678 - // or overflow like 4.65e5324. The size of the string is small - // but can't be held in a Double. - return new BigDecimal(val); - } - return d; - } - // integer representation. - // This will narrow any values to the smallest reasonable Object representation - // (Integer, Long, or BigInteger) - - // string version - // The compare string length method reduces GC, - // but leads to smaller integers being placed in larger wrappers even though not - // needed. i.e. 1,000,000,000 -> Long even though it's an Integer - // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long - // if(val.length()<=9){ - // return Integer.valueOf(val); - // } - // if(val.length()<=18){ - // return Long.valueOf(val); - // } - // return new BigInteger(val); - - // BigInteger version: We use a similar bitLenth compare as - // BigInteger#intValueExact uses. Increases GC, but objects hold - // only what they need. i.e. Less runtime overhead if the value is - // long lived. Which is the better tradeoff? This is closer to what's - // in stringToValue. - BigInteger bi = new BigInteger(val); - if (bi.bitLength() <= 31) { - return Integer.valueOf(bi.intValue()); - } - if (bi.bitLength() <= 63) { - return Long.valueOf(bi.longValue()); - } - return bi; - } - throw new NumberFormatException("val [" + val + "] is not a valid number."); - } - - /** - * Try to convert a string into a number, boolean, or null. If the string can't - * be converted, return the string. - * - * @param string A String. - * @return A simple JSON value. - */ - // Changes to this method must be copied to the corresponding method in - // the XML class to keep full support for Android - public static Object stringToValue(String string) { - if (string.equals("")) { - return string; - } - if (string.equalsIgnoreCase("true")) { - return Boolean.TRUE; - } - if (string.equalsIgnoreCase("false")) { - return Boolean.FALSE; - } - if (string.equalsIgnoreCase("null")) { - return JSONObject.NULL; - } - - /* - * If it might be a number, try converting it. If a number cannot be produced, - * then the value will just be a string. - */ - - char initial = string.charAt(0); - if ((initial >= '0' && initial <= '9') || initial == '-') { - try { - // if we want full Big Number support this block can be replaced with: - // return stringToNumber(string); - if (isDecimalNotation(string)) { - Double d = Double.valueOf(string); - if (!d.isInfinite() && !d.isNaN()) { - return d; - } - } else { - Long myLong = Long.valueOf(string); - if (string.equals(myLong.toString())) { - if (myLong.longValue() == myLong.intValue()) { - return Integer.valueOf(myLong.intValue()); - } - return myLong; - } - } - } catch (Exception ignore) { - } - } - return string; - } - - /** - * Throw an exception if the object is a NaN or infinite number. - * - * @param o The object to test. - * @throws JSONException If o is a non-finite number. - */ - public static void testValidity(Object o) throws JSONException { - if (o != null) { - if (o instanceof Double) { - if (((Double) o).isInfinite() || ((Double) o).isNaN()) { - throw new JSONException("JSON does not allow non-finite numbers."); - } - } else if (o instanceof Float) { - if (((Float) o).isInfinite() || ((Float) o).isNaN()) { - throw new JSONException("JSON does not allow non-finite numbers."); - } - } - } - } - - /** - * Produce a JSONArray containing the values of the members of this JSONObject. - * - * @param names A JSONArray containing a list of key strings. This determines - * the sequence of the values in the result. - * @return A JSONArray of values. - * @throws JSONException If any of the values are non-finite numbers. - */ - public JSONArray toJSONArray(JSONArray names) throws JSONException { - if (names == null || names.isEmpty()) { - return null; - } - JSONArray ja = new JSONArray(); - for (int i = 0; i < names.length(); i += 1) { - ja.put(this.opt(names.getString(i))); - } - return ja; - } - - /** - * Make a JSON text of this JSONObject. For compactness, no whitespace is added. - * If this would not result in a syntactically correct JSON text, then null will - * be returned instead. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a printable, displayable, portable, transmittable representation of - * the object, beginning with { (left - * brace) and ending with } (right - * brace). - */ - @Override - public String toString() { - try { - return this.toString(0); - } catch (Exception e) { - return null; - } - } - - /** - * Make a pretty-printed JSON text of this JSONObject. - * - *

- * If indentFactor > 0 and the {@link JSONObject} has only one key, - * then the object will be output on a single line: - * - *

-	 * {@code {"key": 1}}
-	 * 
- * - *

- * If an object has 2 or more keys, then it will be output across multiple - * lines:

{
-	 *  "key1": 1,
-	 *  "key2": "value 2",
-	 *  "key3": 3
-	 * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param indentFactor The number of spaces to add to each level of indentation. - * @return a printable, displayable, portable, transmittable representation of - * the object, beginning with { (left - * brace) and ending with } (right - * brace). - * @throws JSONException If the object contains an invalid number. - */ - public String toString(int indentFactor) throws JSONException { - StringWriter w = new StringWriter(); - synchronized (w.getBuffer()) { - return this.write(w, indentFactor, 0).toString(); - } - } - - /** - * Make a JSON text of an Object value. If the object has an - * value.toJSONString() method, then that method will be used to produce the - * JSON text. The method is required to produce a strictly conforming text. If - * the object does not contain a toJSONString method (which is the most common - * case), then a text will be produced by other means. If the value is an array - * or Collection, then a JSONArray will be made from it and its toJSONString - * method will be called. If the value is a MAP, then a JSONObject will be made - * from it and its toJSONString method will be called. Otherwise, the value's - * toString method will be called, and the result will be quoted. - * - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param value The value to be serialized. - * @return a printable, displayable, transmittable representation of the object, - * beginning with { (left brace) and - * ending with } (right brace). - * @throws JSONException If the value is or contains an invalid number. - */ - public static String valueToString(Object value) throws JSONException { - // moves the implementation to JSONWriter as: - // 1. It makes more sense to be part of the writer class - // 2. For Android support this method is not available. By implementing it in - // the Writer - // Android users can use the writer with the built in Android JSONObject - // implementation. - return JSONWriter.valueToString(value); - } - - /** - * Wrap an object, if necessary. If the object is null, return the - * NULL object. If it is an array or collection, wrap it in a JSONArray. If it - * is a map, wrap it in a JSONObject. If it is a standard property (Double, - * String, et al) then it is already wrapped. Otherwise, if it comes from one of - * the java packages, turn it into a string. And if it doesn't, try to wrap it - * in a JSONObject. If the wrapping fails, then null is returned. - * - * @param object The object to wrap - * @return The wrapped value - */ - public static Object wrap(Object object) { - try { - if (object == null) { - return NULL; - } - if (object instanceof JSONObject || object instanceof JSONArray || NULL.equals(object) - || object instanceof JSONString || object instanceof Byte || object instanceof Character - || object instanceof Short || object instanceof Integer || object instanceof Long - || object instanceof Boolean || object instanceof Float || object instanceof Double - || object instanceof String || object instanceof BigInteger || object instanceof BigDecimal - || object instanceof Enum) { - return object; - } - - if (object instanceof Collection) { - Collection coll = (Collection) object; - return new JSONArray(coll); - } - if (object.getClass().isArray()) { - return new JSONArray(object); - } - if (object instanceof Map) { - Map map = (Map) object; - return new JSONObject(map); - } - Package objectPackage = object.getClass().getPackage(); - String objectPackageName = objectPackage != null ? objectPackage.getName() : ""; - if (objectPackageName.startsWith("java.") || objectPackageName.startsWith("javax.") - || object.getClass().getClassLoader() == null) { - return object.toString(); - } - return new JSONObject(object); - } catch (Exception exception) { - return null; - } - } - - /** - * Write the contents of the JSONObject as JSON text to a writer. For - * compactness, no whitespace is added. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer) throws JSONException { - return this.write(writer, 0, 0); - } - - static final Writer writeValue(Writer writer, Object value, int indentFactor, int indent) - throws JSONException, IOException { - if (value == null || value.equals(null)) { - writer.write("null"); - } else if (value instanceof JSONString) { - Object o; - try { - o = ((JSONString) value).toJSONString(); - } catch (Exception e) { - throw new JSONException(e); - } - writer.write(o != null ? o.toString() : quote(value.toString())); - } else if (value instanceof Number) { - // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary - final String numberAsString = numberToString((Number) value); - try { - // Use the BigDecimal constructor for its parser to validate the format. - @SuppressWarnings("unused") - BigDecimal testNum = new BigDecimal(numberAsString); - // Close enough to a JSON number that we will use it unquoted - writer.write(numberAsString); - } catch (NumberFormatException ex) { - // The Number value is not a valid JSON number. - // Instead we will quote it as a string - quote(numberAsString, writer); - } - } else if (value instanceof Boolean) { - writer.write(value.toString()); - } else if (value instanceof Enum) { - writer.write(quote(((Enum) value).name())); - } else if (value instanceof JSONObject) { - ((JSONObject) value).write(writer, indentFactor, indent); - } else if (value instanceof JSONArray) { - ((JSONArray) value).write(writer, indentFactor, indent); - } else if (value instanceof Map) { - Map map = (Map) value; - new JSONObject(map).write(writer, indentFactor, indent); - } else if (value instanceof Collection) { - Collection coll = (Collection) value; - new JSONArray(coll).write(writer, indentFactor, indent); - } else if (value.getClass().isArray()) { - new JSONArray(value).write(writer, indentFactor, indent); - } else { - quote(value.toString(), writer); - } - return writer; - } - - static final void indent(Writer writer, int indent) throws IOException { - for (int i = 0; i < indent; i += 1) { - writer.write(' '); - } - } - - /** - * Write the contents of the JSONObject as JSON text to a writer. - * - *

- * If indentFactor > 0 and the {@link JSONObject} has only one key, - * then the object will be output on a single line: - * - *

-	 * {@code {"key": 1}}
-	 * 
- * - *

- * If an object has 2 or more keys, then it will be output across multiple - * lines:

{
-	 *  "key1": 1,
-	 *  "key2": "value 2",
-	 *  "key3": 3
-	 * }
- *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param writer Writes the serialized JSON - * @param indentFactor The number of spaces to add to each level of indentation. - * @param indent The indentation of the top level. - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer, int indentFactor, int indent) throws JSONException { - try { - boolean commanate = false; - final int length = this.length(); - writer.write('{'); - - if (length == 1) { - final Entry entry = this.entrySet().iterator().next(); - final String key = entry.getKey(); - writer.write(quote(key)); - writer.write(':'); - if (indentFactor > 0) { - writer.write(' '); - } - try { - writeValue(writer, entry.getValue(), indentFactor, indent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONObject value for key: " + key, e); - } - } else if (length != 0) { - final int newindent = indent + indentFactor; - for (final Entry entry : this.entrySet()) { - if (commanate) { - writer.write(','); - } - if (indentFactor > 0) { - writer.write('\n'); - } - indent(writer, newindent); - final String key = entry.getKey(); - writer.write(quote(key)); - writer.write(':'); - if (indentFactor > 0) { - writer.write(' '); - } - try { - writeValue(writer, entry.getValue(), indentFactor, newindent); - } catch (Exception e) { - throw new JSONException("Unable to write JSONObject value for key: " + key, e); - } - commanate = true; - } - if (indentFactor > 0) { - writer.write('\n'); - } - indent(writer, indent); - } - writer.write('}'); - return writer; - } catch (IOException exception) { - throw new JSONException(exception); - } - } - - /** - * Returns a java.util.Map containing all of the entries in this object. If an - * entry in the object is a JSONArray or JSONObject it will also be converted. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a java.util.Map containing the entries of this object - */ - public Map toMap() { - Map results = new HashMap(); - for (Entry entry : this.entrySet()) { - Object value; - if (entry.getValue() == null || NULL.equals(entry.getValue())) { - value = null; - } else if (entry.getValue() instanceof JSONObject) { - value = ((JSONObject) entry.getValue()).toMap(); - } else if (entry.getValue() instanceof JSONArray) { - value = ((JSONArray) entry.getValue()).toList(); - } else { - value = entry.getValue(); - } - results.put(entry.getKey(), value); - } - return results; - } +public class JSONObject +{ + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, + * whilst Java's null is equivalent to the value that JavaScript calls + * undefined. + */ + private static final class Null + { + + /** + * There is only intended to be a single instance of the NULL object, so the + * clone method returns itself. + * + * @return NULL. + */ + @Override + protected final Object clone() + { + return this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @param object + * An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or + * null. + */ + @Override + public boolean equals(Object object) + { + return object == null || object == this; + } + + /** + * A Null object is equal to the null value and to itself. + * + * @return always returns 0. + */ + @Override + public int hashCode() + { + return 0; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() + { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() + { + // HashMap is used on purpose to ensure that elements are unordered by + // the specification. + // JSON tends to be a portable transfer format to allows the container + // implementations to rearrange their items for a faster element + // retrieval based on associative access. + // Therefore, an implementation mustn't rely on the order of the item. + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of + * strings is used to identify the keys that should be copied. Missing keys + * are ignored. + * + * @param jo + * A JSONObject. + * @param names + * An array of strings. + */ + public JSONObject(JSONObject jo, String[] names) + { + this(names.length); + for (int i = 0; i < names.length; i += 1) + { + try + { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) + { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a duplicated + * key. + */ + public JSONObject(JSONTokener x) throws JSONException + { + this(); + char c; + String key; + + if (x.nextClean() != '{') + { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) + { + c = x.nextClean(); + switch (c) + { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. + + c = x.nextClean(); + if (c != ':') + { + throw x.syntaxError("Expected a ':' after a key"); + } + + // Use syntaxError(..) to include error location + + if (key != null) + { + // Check if key exists + if (this.opt(key) != null) + { + // key already exists + throw x.syntaxError("Duplicate key \"" + key + "\""); + } + // Only add value if non-null + Object value = x.nextValue(); + if (value != null) + { + this.put(key, value); + } + } + + // Pairs are separated by ','. + + switch (x.nextClean()) + { + case ';': + case ',': + if (x.nextClean() == '}') + { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param m + * A map object that can be used to initialize the contents of the + * JSONObject. + * @throws JSONException + * If a value in the map is non-finite number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONObject(Map m) + { + if (m == null) + { + this.map = new HashMap(); + } + else + { + this.map = new HashMap(m.size()); + for (final Entry e : m.entrySet()) + { + if (e.getKey() == null) + { + throw new NullPointerException("Null key."); + } + final Object value = e.getValue(); + if (value != null) + { + this.map.put(String.valueOf(e.getKey()), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or "is" + * followed by an uppercase letter, the method is invoked, and a key and the + * value returned from the getter method are put into the new JSONObject. + *

+ * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the first + * character is converted to lower case. + *

+ * Methods that are static, return void, have + * parameters, or are "bridge" methods, are ignored. + *

+ * For example, if an object has a method named "getName", and if + * the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + *

+ * The {@link JSONPropertyName} annotation can be used on a bean getter to + * override key name used in the JSONObject. For example, using the object + * above with the getName method, if we annotated it with: + * + *

+   * @JSONPropertyName("FullName")
+   * public String getName()
+   * {
+   *   return this.name;
+   * }
+   * 
+ * + * The resulting JSON object would contain + * "FullName": "Larry Fine" + *

+ * Similarly, the {@link JSONPropertyName} annotation can be used on non- + * get and is methods. We can also override key name + * used in the JSONObject as seen below even though the field would normally + * be ignored: + * + *

+   * @JSONPropertyName("FullName")
+   * public String fullName()
+   * {
+   *   return this.name;
+   * }
+   * 
+ * + * The resulting JSON object would contain + * "FullName": "Larry Fine" + *

+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean + * property to not be serialized into JSON. If both {@link JSONPropertyIgnore} + * and {@link JSONPropertyName} are defined on the same method, a depth + * comparison is performed and the one closest to the concrete class being + * serialized is used. If both annotations are at the same level, then the + * {@link JSONPropertyIgnore} annotation takes precedent and the field is not + * serialized. For example, the following declaration would prevent the + * getName method from being serialized: + * + *

+   * @JSONPropertyName("FullName")
+   * @JSONPropertyIgnore
+   * public String getName()
+   * {
+   *   return this.name;
+   * }
+   * 
+ *

+ * + * @param bean + * An object that has getter methods that should be used to make a + * JSONObject. + */ + public JSONObject(Object bean) + { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the public + * members. The resulting JSONObject's keys will be the strings from the names + * array, and the values will be the field values associated with those keys + * in the object. If a key is not found or not visible, then it will not be + * copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained from + * the object. + */ + public JSONObject(Object object, String names[]) + { + this(names.length); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) + { + String name = names[i]; + try + { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) + { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with }  (right + * brace). + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException + { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException + { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) + { + Object key = keys.nextElement(); + if (key != null) + { + + // Go through the path, ensuring that there is a nested JSONObject for + // each + // segment except the last. Add the value using the last segment's name + // into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) + { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) + { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Constructor to specify an initial capacity of the internal map. Useful for + * library internal calls where we know, or at least can best guess, how big + * this JSONObject will be. + * + * @param initialCapacity + * initial capacity of the internal map. + */ + protected JSONObject(int initialCapacity) + { + this.map = new HashMap(initialCapacity); + } + + /** + * Accumulate values under a key. It is similar to the put method except that + * if there is already an object stored under the key then a JSONArray is + * stored under the key to hold all of the accumulated values. If there is + * already a JSONArray, then the new value is appended to it. In contrast, the + * put method replaces the previous value. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, then + * the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject accumulate(String key, Object value) + throws JSONException + { + testValidity(value); + Object object = this.opt(key); + if (object == null) + { + this.put(key, value instanceof JSONArray ? new JSONArray().put(value) + : value); + } + else if (object instanceof JSONArray) + { + ((JSONArray) object).put(value); + } + else + { + this.put(key, new JSONArray().put(object).put(value)); + } + return this; + } + + /** + * Append values to the array under a key. If the key does not exist in the + * JSONObject, then the key is put in the JSONObject with its value being a + * JSONArray containing the value parameter. If the key was already associated + * with a JSONArray, then the value parameter is appended to it. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is non-finite number or if the current value + * associated with the key is not a JSONArray. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject append(String key, Object value) throws JSONException + { + testValidity(value); + Object object = this.opt(key); + if (object == null) + { + this.put(key, new JSONArray().put(value)); + } + else if (object instanceof JSONArray) + { + this.put(key, ((JSONArray) object).put(value)); + } + else + { + throw new JSONException( + "JSONObject[" + key + "] is not a JSONArray."); + } + return this; + } + + /** + * Produce a string from a double. The string "null" will be returned if the + * number is not finite. + * + * @param d + * A double. + * @return A String. + */ + public static String doubleToString(double d) + { + if (Double.isInfinite(d) || Double.isNaN(d)) + { + return "null"; + } + + // Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) + { + while (string.endsWith("0")) + { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) + { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException + { + if (key == null) + { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) + { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key + * @throws JSONException + * if the key is not found or if the value cannot be converted to an + * enum. + */ + public > E getEnum(Class clazz, String key) + throws JSONException + { + E val = optEnum(clazz, key); + if (val == null) + { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException( + "JSONObject[" + quote(key) + "] is not an enum of type " + + quote(clazz.getSimpleName()) + "."); + } + return val; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or "false". + */ + public boolean getBoolean(String key) throws JSONException + { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) || (object instanceof String + && ((String) object).equalsIgnoreCase("false"))) + { + return false; + } + else if (object.equals(Boolean.TRUE) || (object instanceof String + && ((String) object).equalsIgnoreCase("true"))) + { + return true; + } + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a Boolean."); + } + + /** + * Get the BigInteger value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value cannot be converted to + * BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException + { + Object object = this.get(key); + try + { + return new BigInteger(object.toString()); + } catch (Exception e) + { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger.", e); + } + } + + /** + * Get the BigDecimal value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value cannot be converted to + * BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException + { + Object object = this.get(key); + if (object instanceof BigDecimal) + { + return (BigDecimal) object; + } + try + { + return new BigDecimal(object.toString()); + } catch (Exception e) + { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal.", e); + } + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number object + * and cannot be converted to a number. + */ + public double getDouble(String key) throws JSONException + { + Object object = this.get(key); + try + { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble(object.toString()); + } catch (Exception e) + { + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the float value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number object + * and cannot be converted to a number. + */ + public float getFloat(String key) throws JSONException + { + Object object = this.get(key); + try + { + return object instanceof Number ? ((Number) object).floatValue() + : Float.parseFloat(object.toString()); + } catch (Exception e) + { + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number object + * and cannot be converted to a number. + */ + public Number getNumber(String key) throws JSONException + { + Object object = this.get(key); + try + { + if (object instanceof Number) + { + return (Number) object; + } + return stringToNumber(object.toString()); + } catch (Exception e) + { + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a number.", e); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted to an + * integer. + */ + public int getInt(String key) throws JSONException + { + Object object = this.get(key); + try + { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) + { + throw new JSONException( + "JSONObject[" + quote(key) + "] is not an int.", e); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException + { + Object object = this.get(key); + if (object instanceof JSONArray) + { + return (JSONArray) object; + } + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a JSONArray."); + } + + /** + * Get the JSONObject value associated with a key. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException + { + Object object = this.get(key); + if (object instanceof JSONObject) + { + return (JSONObject) object; + } + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted to a + * long. + */ + public long getLong(String key) throws JSONException + { + Object object = this.get(key); + try + { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) + { + throw new JSONException( + "JSONObject[" + quote(key) + "] is not a long.", e); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) + { + if (jo.isEmpty()) + { + return null; + } + return jo.keySet().toArray(new String[jo.length()]); + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) + { + if (object == null) + { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) + { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) + { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException + { + Object object = this.get(key); + if (object instanceof String) + { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * Determine if the JSONObject contains a specific key. + * + * @param key + * A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) + { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, create + * one with a value of 1. If there is such a property, and if it is an + * Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException + { + Object value = this.opt(key); + if (value == null) + { + this.put(key, 1); + } + else if (value instanceof BigInteger) + { + this.put(key, ((BigInteger) value).add(BigInteger.ONE)); + } + else if (value instanceof BigDecimal) + { + this.put(key, ((BigDecimal) value).add(BigDecimal.ONE)); + } + else if (value instanceof Integer) + { + this.put(key, ((Integer) value).intValue() + 1); + } + else if (value instanceof Long) + { + this.put(key, ((Long) value).longValue() + 1L); + } + else if (value instanceof Double) + { + this.put(key, ((Double) value).doubleValue() + 1.0d); + } + else if (value instanceof Float) + { + this.put(key, ((Float) value).floatValue() + 1.0f); + } + else + { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * Determine if the value associated with the key is null or if + * there is no value. + * + * @param key + * A key string. + * @return true if there is no value associated with the key or if the value + * is the JSONObject.NULL object. + */ + public boolean isNull(String key) + { + return JSONObject.NULL.equals(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. Modifying this key Set + * will also modify the JSONObject. Use with caution. + * + * @see Set#iterator() + * + * @return An iterator of the keys. + */ + public Iterator keys() + { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. Modifying this key Set will also + * modify the JSONObject. Use with caution. + * + * @see Map#keySet() + * + * @return A keySet. + */ + public Set keySet() + { + return this.map.keySet(); + } + + /** + * Get a set of entries of the JSONObject. These are raw values and may not + * match what is returned by the JSONObject get* and opt* functions. Modifying + * the returned EntrySet or the Entry objects contained therein will modify + * the backing JSONObject. This does not return a clone or a read-only view. + * + * Use with caution. + * + * @see Map#entrySet() + * + * @return An Entry Set + */ + protected Set> entrySet() + { + return this.map.entrySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() + { + return this.map.size(); + } + + /** + * Check if JSONObject is empty. + * + * @return true if JSONObject is empty, otherwise false. + */ + public boolean isEmpty() + { + return map.isEmpty(); + } + + /** + * Produce a JSONArray containing the names of the elements of this + * JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject + * is empty. + */ + public JSONArray names() + { + if (this.map.isEmpty()) + { + return null; + } + return new JSONArray(this.map.keySet()); + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException + { + if (number == null) + { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) + { + while (string.endsWith("0")) + { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) + { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an optional value associated with a key. + * + * @param key + * A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) + { + return key == null ? null : this.map.get(key); + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key or null if not found + */ + public > E optEnum(Class clazz, String key) + { + return this.optEnum(clazz, key, null); + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @param defaultValue + * The default in case the value is not found + * @return The enum value associated with the key or defaultValue if the value + * is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, String key, + E defaultValue) + { + try + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) + { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException e) + { + return defaultValue; + } catch (NullPointerException e) + { + return defaultValue; + } + } + + /** + * Get an optional boolean associated with a key. It returns false if there is + * no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) + { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the defaultValue + * if there is no such key, or if it is not a Boolean or the String "true" or + * "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Boolean) + { + return ((Boolean) val).booleanValue(); + } + try + { + // we'll use the get anyway because it does string conversion. + return this.getBoolean(key); + } catch (Exception e) + { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof BigDecimal) + { + return (BigDecimal) val; + } + if (val instanceof BigInteger) + { + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float) + { + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) + { + return new BigDecimal(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try + { + return new BigDecimal(val.toString()); + } catch (Exception e) + { + return defaultValue; + } + } + + /** + * Get an optional BigInteger associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof BigInteger) + { + return (BigInteger) val; + } + if (val instanceof BigDecimal) + { + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float) + { + return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte) + { + return BigInteger.valueOf(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try + { + // the other opt functions handle implicit conversions, i.e. + // jo.put("double",1.1d); + // jo.optInt("double"); -- will return 1, not an error + // this conversion to BigDecimal then to BigInteger is to maintain + // that type cast support that may truncate the decimal. + final String valStr = val.toString(); + if (isDecimalNotation(valStr)) + { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) + { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) + { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if there + * is no such key or if its value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Number) + { + return ((Number) val).doubleValue(); + } + if (val instanceof String) + { + try + { + return Double.parseDouble((String) val); + } catch (Exception e) + { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional double value associated with an index. NaN is returned if + * there is no value for the index, or if the value is not a number and cannot + * be converted to a number. + * + * @param key + * A key string. + * @return The value. + */ + public float optFloat(String key) + { + return this.optFloat(key, Float.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param key + * A key string. + * @param defaultValue + * The default value. + * @return The value. + */ + public float optFloat(String key, float defaultValue) + { + Object val = this.opt(key); + if (JSONObject.NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Number) + { + return ((Number) val).floatValue(); + } + if (val instanceof String) + { + try + { + return Float.parseFloat((String) val); + } catch (Exception e) + { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) + { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there is + * no such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Number) + { + return ((Number) val).intValue(); + } + + if (val instanceof String) + { + try + { + return new BigDecimal((String) val).intValue(); + } catch (Exception e) + { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) + { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if there + * is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) + { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) + { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Number) + { + return ((Number) val).longValue(); + } + + if (val instanceof String) + { + try + { + return new BigDecimal((String) val).longValue(); + } catch (Exception e) + { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional {@link Number} value associated with a key, or + * null if there is no such key or if the value is not a number. + * If the value is a string, an attempt will be made to evaluate it as a + * number ({@link BigDecimal}). This method would be used in cases where type + * coercion of the number value is unwanted. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public Number optNumber(String key) + { + return this.optNumber(key, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default + * if there is no such key or if the value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public Number optNumber(String key, Number defaultValue) + { + Object val = this.opt(key); + if (NULL.equals(val)) + { + return defaultValue; + } + if (val instanceof Number) + { + return (Number) val; + } + + if (val instanceof String) + { + try + { + return stringToNumber((String) val); + } catch (Exception e) + { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional string associated with a key. It returns an empty string if + * there is no such key. If the value is not a string and is not null, then it + * is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) + { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) + { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Populates the internal map of the JSONObject with the bean properties. The + * bean can not be recursive. + * + * @see JSONObject#JSONObject(Object) + * + * @param bean + * the bean + */ + private void populateMap(Object bean) + { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() + : klass.getDeclaredMethods(); + for (final Method method : methods) + { + final int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) + && method.getParameterTypes().length == 0 + && !method.isBridge() && method.getReturnType() != Void.TYPE + && isValidMethodName(method.getName())) + { + final String key = getKeyNameFromMethod(method); + if (key != null && !key.isEmpty()) + { + try + { + final Object result = method.invoke(bean); + if (result != null) + { + this.map.put(key, wrap(result)); + // we don't use the result anywhere outside of wrap + // if it's a resource we should be sure to close it + // after calling toString + if (result instanceof Closeable) + { + try + { + ((Closeable) result).close(); + } catch (IOException ignore) + { + } + } + } + } catch (IllegalAccessException ignore) + { + } catch (IllegalArgumentException ignore) + { + } catch (InvocationTargetException ignore) + { + } + } + } + } + } + + private boolean isValidMethodName(String name) + { + return !"getClass".equals(name) && !"getDeclaringClass".equals(name); + } + + private String getKeyNameFromMethod(Method method) + { + final int ignoreDepth = -1;// getAnnotationDepth(method, + // JSONPropertyIgnore.class); + // if (ignoreDepth > 0) { + // final int forcedNameDepth = getAnnotationDepth(method, + // JSONPropertyName.class); + // if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { + // // the hierarchy asked to ignore, and the nearest name override + // // was higher or non-existent + // return null; + // } + // } + // JSONPropertyName annotation = getAnnotation(method, + // JSONPropertyName.class); + // if (annotation != null && annotation.value() != null && + // !annotation.value().isEmpty()) { + // return annotation.value(); + // } + String key; + final String name = method.getName(); + if (name.startsWith("get") && name.length() > 3) + { + key = name.substring(3); + } + else if (name.startsWith("is") && name.length() > 2) + { + key = name.substring(2); + } + else + { + return null; + } + // if the first letter in the key is not uppercase, then skip. + // This is to maintain backwards compatibility before PR406 + // (https://github.com/stleary/JSON-java/pull/406/) + if (Character.isLowerCase(key.charAt(0))) + { + return null; + } + if (key.length() == 1) + { + key = key.toLowerCase(Locale.ROOT); + } + else if (!Character.isUpperCase(key.charAt(1))) + { + key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); + } + return (/** @j2sNative 1 ? key.split("$")[0] : */ + key); + } + + // /** + // * Searches the class hierarchy to see if the method or it's super + // * implementations and interfaces has the annotation. + // * + // * @param + // * type of the annotation + // * + // * @param m + // * method to check + // * @param annotationClass + // * annotation to look for + // * @return the {@link Annotation} if the annotation exists on the current + // method + // * or one of it's super class definitions + // */ + // private static A getAnnotation(final Method m, final + // Class annotationClass) { + // return null; + // // if we have invalid data the result is null + // if (true || m == null || annotationClass == null) { + // return null; + // } + // + // if (m.isAnnotationPresent(annotationClass)) { + // return m.getAnnotation(annotationClass); + // } + // + // // if we've already reached the Object class, return null; + // Class c = m.getDeclaringClass(); + // if (c.getSuperclass() == null) { + // return null; + // } + // + // // check directly implemented interfaces for the method being checked + // for (Class i : c.getInterfaces()) { + // try { + // Method im = i.getMethod(m.getName(), m.getParameterTypes()); + // return getAnnotation(im, annotationClass); + // } catch (final SecurityException ex) { + // continue; + // } catch (final NoSuchMethodException ex) { + // continue; + // } + // } + // + // try { + // return getAnnotation( + // c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + // annotationClass); + // } catch (final SecurityException ex) { + // return null; + // } catch (final NoSuchMethodException ex) { + // return null; + // } + // } + // + // /** + // * Searches the class hierarchy to see if the method or it's super + // * implementations and interfaces has the annotation. Returns the depth of + // the + // * annotation in the hierarchy. + // * + // * @param + // * type of the annotation + // * + // * @param m + // * method to check + // * @param annotationClass + // * annotation to look for + // * @return Depth of the annotation or -1 if the annotation is not on the + // method. + // */ + // private static int getAnnotationDepth(final Method m, final Class annotationClass) { + // // if we have invalid data the result is -1 + // if (m == null || annotationClass == null) { + // return -1; + // } + // if (m.isAnnotationPresent(annotationClass)) { + // return 1; + // } + // + // // if we've already reached the Object class, return -1; + // Class c = m.getDeclaringClass(); + // if (c.getSuperclass() == null) { + // return -1; + // } + // + // // check directly implemented interfaces for the method being checked + // for (Class i : c.getInterfaces()) { + // try { + // Method im = i.getMethod(m.getName(), m.getParameterTypes()); + // int d = getAnnotationDepth(im, annotationClass); + // if (d > 0) { + // // since the annotation was on the interface, add 1 + // return d + 1; + // } + // } catch (final SecurityException ex) { + // continue; + // } catch (final NoSuchMethodException ex) { + // continue; + // } + // } + // + // try { + // int d = getAnnotationDepth( + // c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + // annotationClass); + // if (d > 0) { + // // since the annotation was on the superclass, add 1 + // return d + 1; + // } + // return -1; + // } catch (final SecurityException ex) { + // return -1; + // } catch (final NoSuchMethodException ex) { + // return -1; + // } + // } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException + { + return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONArray + * which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Collection value) + throws JSONException + { + return this.put(key, new JSONArray(value)); + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, double value) throws JSONException + { + return this.put(key, Double.valueOf(value)); + } + + /** + * Put a key/float pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A float which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, float value) throws JSONException + { + return this.put(key, Float.valueOf(value)); + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException + { + return this.put(key, Integer.valueOf(value)); + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException + { + return this.put(key, Long.valueOf(value)); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Map value) throws JSONException + { + return this.put(key, new JSONObject(value)); + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, + * then the key will be removed from the JSONObject if it is present. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these types: + * Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException + { + if (key == null) + { + throw new NullPointerException("Null key."); + } + if (value != null) + { + testValidity(value); + this.map.put(key, value); + } + else + { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key + * string + * @param value + * object + * @return this. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException + { + if (key != null && value != null) + { + if (this.opt(key) != null) + { + throw new JSONException("Duplicate key \"" + key + "\""); + } + return this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @param key + * A key string. + * @param value + * An object which is the value. It should be of one of these types: + * Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or + * the JSONObject.NULL object. + * @return this. + * @throws JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException + { + if (key != null && value != null) + { + return this.put(key, value); + } + return this; + } + + /** + * Creates a JSONPointer using an initialization string and tries to match it + * to an item within this JSONObject. For example, given a JSONObject + * initialized with this document: + * + *

+   * {
+   *     "a":{"b":"c"}
+   * }
+   * 
+ * + * and this JSONPointer string: + * + *
+   * "/a/b"
+   * 
+ * + * Then this method will return the String "c". A JSONPointerException may be + * thrown from code called by this method. + * + * @param jsonPointer + * string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer) + { + return query(new JSONPointer(jsonPointer)); + } + + /** + * Uses a user initialized JSONPointer and tries to match it to an item within + * this JSONObject. For example, given a JSONObject initialized with this + * document: + * + *
+   * {
+   *     "a":{"b":"c"}
+   * }
+   * 
+ * + * and this JSONPointer: + * + *
+   * "/a/b"
+   * 
+ * + * Then this method will return the String "c". A JSONPointerException may be + * thrown from code called by this method. + * + * @param jsonPointer + * string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer) + { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer + * the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException + * if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) + { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer + * The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException + * if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) + { + try + { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) + { + return null; + } + } + + /** + * Produce a string in double quotes with backslash sequences in all the right + * places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) + { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } + else + { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * Remove a name and its value, if present. + * + * @param key + * The name to be removed. + * @return The value that was associated with the name, or null if there was + * no value. + */ + public Object remove(String key) + { + return this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. They must contain the same set of + * names which must be associated with similar values. + * + * @param other + * The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) + { + try + { + if (!(other instanceof JSONObject)) + { + return false; + } + if (!this.keySet().equals(((JSONObject) other).keySet())) + { + return false; + } + for (final Entry entry : this.entrySet()) + { + String name = entry.getKey(); + Object valueThis = entry.getValue(); + Object valueOther = ((JSONObject) other).get(name); + if (valueThis == valueOther) + { + continue; + } + if (valueThis == null) + { + return false; + } + if (valueThis instanceof JSONObject) + { + if (!((JSONObject) valueThis).similar(valueOther)) + { + return false; + } + } + else if (valueThis instanceof JSONArray) + { + if (!((JSONArray) valueThis).similar(valueOther)) + { + return false; + } + } + else if (!valueThis.equals(valueOther)) + { + return false; + } + } + return true; + } catch (Throwable exception) + { + return false; + } + } + + /** + * Tests if the value should be tried as a decimal. It makes no test if there + * are actual digits. + * + * @param val + * value to test + * @return true if the string is "-0" or if it contains '.', 'e', or 'E', + * false otherwise. + */ + protected static boolean isDecimalNotation(final String val) + { + return val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 || "-0".equals(val); + } + + /** + * Converts a string to a number using the narrowest possible type. Possible + * returns for this function are BigDecimal, Double, BigInteger, Long, and + * Integer. When a Double is returned, it should always be a valid Double and + * not NaN or +-infinity. + * + * @param val + * value to convert + * @return Number representation of the value. + * @throws NumberFormatException + * thrown if the value is not a valid number. A public caller should + * catch this and wrap it in a {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) + throws NumberFormatException + { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') + { + // decimal representation + if (isDecimalNotation(val)) + { + // quick dirty way to see if we need a BigDecimal instead of a Double + // this only handles some cases of overflow or underflow + if (val.length() > 14) + { + return new BigDecimal(val); + } + final Double d = Double.valueOf(val); + if (d.isInfinite() || d.isNaN()) + { + // if we can't parse it as a double, go up to BigDecimal + // this is probably due to underflow like 4.32e-678 + // or overflow like 4.65e5324. The size of the string is small + // but can't be held in a Double. + return new BigDecimal(val); + } + return d; + } + // integer representation. + // This will narrow any values to the smallest reasonable Object + // representation + // (Integer, Long, or BigInteger) + + // string version + // The compare string length method reduces GC, + // but leads to smaller integers being placed in larger wrappers even + // though not + // needed. i.e. 1,000,000,000 -> Long even though it's an Integer + // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long + // if(val.length()<=9){ + // return Integer.valueOf(val); + // } + // if(val.length()<=18){ + // return Long.valueOf(val); + // } + // return new BigInteger(val); + + // BigInteger version: We use a similar bitLenth compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. Which is the better tradeoff? This is closer to what's + // in stringToValue. + BigInteger bi = new BigInteger(val); + if (bi.bitLength() <= 31) + { + return Integer.valueOf(bi.intValue()); + } + if (bi.bitLength() <= 63) + { + return Long.valueOf(bi.longValue()); + } + return bi; + } + throw new NumberFormatException( + "val [" + val + "] is not a valid number."); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. + * @return A simple JSON value. + */ + // Changes to this method must be copied to the corresponding method in + // the XML class to keep full support for Android + public static Object stringToValue(String string) + { + if (string.equals("")) + { + return string; + } + if (string.equalsIgnoreCase("true")) + { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) + { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) + { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be produced, + * then the value will just be a string. + */ + + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') + { + try + { + // if we want full Big Number support this block can be replaced with: + // return stringToNumber(string); + if (isDecimalNotation(string)) + { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) + { + return d; + } + } + else + { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) + { + if (myLong.longValue() == myLong.intValue()) + { + return Integer.valueOf(myLong.intValue()); + } + return myLong; + } + } + } catch (Exception ignore) + { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException + { + if (o != null) + { + if (o instanceof Double) + { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) + { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + else if (o instanceof Float) + { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) + { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines the + * sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException + { + if (names == null || names.isEmpty()) + { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) + { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is + * added. If this would not result in a syntactically correct JSON text, then + * null will be returned instead. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation of + * the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + @Override + public String toString() + { + try + { + return this.toString(0); + } catch (Exception e) + { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONObject. + * + *

+ * If indentFactor > 0 and the {@link JSONObject} has only one + * key, then the object will be output on a single line: + * + *

+   * {@code {"key": 1}}
+   * 
+ * + *

+ * If an object has 2 or more keys, then it will be output across multiple + * lines:

{
+   *  "key1": 1,
+   *  "key2": "value 2",
+   *  "key3": 3
+   * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation of + * the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException + { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) + { + return this.write(w, indentFactor, 0).toString(); + } + } + + /** + * Make a JSON text of an Object value. If the object has an + * value.toJSONString() method, then that method will be used to produce the + * JSON text. The method is required to produce a strictly conforming text. If + * the object does not contain a toJSONString method (which is the most common + * case), then a text will be produced by other means. If the value is an + * array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be called. + * Otherwise, the value's toString method will be called, and the result will + * be quoted. + * + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param value + * The value to be serialized. + * @return a printable, displayable, transmittable representation of the + * object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException + { + // moves the implementation to JSONWriter as: + // 1. It makes more sense to be part of the writer class + // 2. For Android support this method is not available. By implementing it + // in + // the Writer + // Android users can use the writer with the built in Android JSONObject + // implementation. + return JSONWriter.valueToString(value); + } + + /** + * Wrap an object, if necessary. If the object is null, return + * the NULL object. If it is an array or collection, wrap it in a JSONArray. + * If it is a map, wrap it in a JSONObject. If it is a standard property + * (Double, String, et al) then it is already wrapped. Otherwise, if it comes + * from one of the java packages, turn it into a string. And if it doesn't, + * try to wrap it in a JSONObject. If the wrapping fails, then null is + * returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) + { + try + { + if (object == null) + { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal || object instanceof Enum) + { + return object; + } + + if (object instanceof Collection) + { + Collection coll = (Collection) object; + return new JSONArray(coll); + } + if (object.getClass().isArray()) + { + return new JSONArray(object); + } + if (object instanceof Map) + { + Map map = (Map) object; + return new JSONObject(map); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null + ? objectPackage.getName() + : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) + { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) + { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException + { + return this.write(writer, 0, 0); + } + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException + { + if (value == null || value.equals(null)) + { + writer.write("null"); + } + else if (value instanceof JSONString) + { + Object o; + try + { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) + { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } + else if (value instanceof Number) + { + // not all Numbers may match actual JSON Numbers. i.e. fractions or + // Imaginary + final String numberAsString = numberToString((Number) value); + try + { + // Use the BigDecimal constructor for its parser to validate the format. + @SuppressWarnings("unused") + BigDecimal testNum = new BigDecimal(numberAsString); + // Close enough to a JSON number that we will use it unquoted + writer.write(numberAsString); + } catch (NumberFormatException ex) + { + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + quote(numberAsString, writer); + } + } + else if (value instanceof Boolean) + { + writer.write(value.toString()); + } + else if (value instanceof Enum) + { + writer.write(quote(((Enum) value).name())); + } + else if (value instanceof JSONObject) + { + ((JSONObject) value).write(writer, indentFactor, indent); + } + else if (value instanceof JSONArray) + { + ((JSONArray) value).write(writer, indentFactor, indent); + } + else if (value instanceof Map) + { + Map map = (Map) value; + new JSONObject(map).write(writer, indentFactor, indent); + } + else if (value instanceof Collection) + { + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indentFactor, indent); + } + else if (value.getClass().isArray()) + { + new JSONArray(value).write(writer, indentFactor, indent); + } + else + { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException + { + for (int i = 0; i < indent; i += 1) + { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * + *

+ * If indentFactor > 0 and the {@link JSONObject} has only one + * key, then the object will be output on a single line: + * + *

+   * {@code {"key": 1}}
+   * 
+ * + *

+ * If an object has 2 or more keys, then it will be output across multiple + * lines:

{
+   *  "key1": 1,
+   *  "key2": "value 2",
+   *  "key3": 3
+   * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @param writer + * Writes the serialized JSON + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer, int indentFactor, int indent) + throws JSONException + { + try + { + boolean commanate = false; + final int length = this.length(); + writer.write('{'); + + if (length == 1) + { + final Entry entry = this.entrySet().iterator().next(); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) + { + writer.write(' '); + } + try + { + writeValue(writer, entry.getValue(), indentFactor, indent); + } catch (Exception e) + { + throw new JSONException( + "Unable to write JSONObject value for key: " + key, e); + } + } + else if (length != 0) + { + final int newindent = indent + indentFactor; + for (final Entry entry : this.entrySet()) + { + if (commanate) + { + writer.write(','); + } + if (indentFactor > 0) + { + writer.write('\n'); + } + indent(writer, newindent); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) + { + writer.write(' '); + } + try + { + writeValue(writer, entry.getValue(), indentFactor, newindent); + } catch (Exception e) + { + throw new JSONException( + "Unable to write JSONObject value for key: " + key, e); + } + commanate = true; + } + if (indentFactor > 0) + { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) + { + throw new JSONException(exception); + } + } + + /** + * Returns a java.util.Map containing all of the entries in this object. If an + * entry in the object is a JSONArray or JSONObject it will also be converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.Map containing the entries of this object + */ + public Map toMap() + { + Map results = new HashMap(); + for (Entry entry : this.entrySet()) + { + Object value; + if (entry.getValue() == null || NULL.equals(entry.getValue())) + { + value = null; + } + else if (entry.getValue() instanceof JSONObject) + { + value = ((JSONObject) entry.getValue()).toMap(); + } + else if (entry.getValue() instanceof JSONArray) + { + value = ((JSONArray) entry.getValue()).toList(); + } + else + { + value = entry.getValue(); + } + results.put(entry.getKey(), value); + } + return results; + } }