Javadocs for JSONObject.

Change-Id: I5ec9df6a3a9baac8f4f498890cd35feff774737a
diff --git a/libcore/json/src/main/java/org/json/JSON.java b/libcore/json/src/main/java/org/json/JSON.java
index 029884b..b32124d 100644
--- a/libcore/json/src/main/java/org/json/JSON.java
+++ b/libcore/json/src/main/java/org/json/JSON.java
@@ -58,7 +58,7 @@
             return ((Number) value).intValue();
         } else if (value instanceof String) {
             try {
-                return Double.valueOf((String) value).intValue();
+                return (int) Double.parseDouble((String) value);
             } catch (NumberFormatException e) {
             }
         }
@@ -72,7 +72,7 @@
             return ((Number) value).longValue();
         } else if (value instanceof String) {
             try {
-                return Double.valueOf((String) value).longValue();
+                return (long) Double.parseDouble((String) value);
             } catch (NumberFormatException e) {
             }
         }
diff --git a/libcore/json/src/main/java/org/json/JSONObject.java b/libcore/json/src/main/java/org/json/JSONObject.java
index c92d549..fcc5d6a 100644
--- a/libcore/json/src/main/java/org/json/JSONObject.java
+++ b/libcore/json/src/main/java/org/json/JSONObject.java
@@ -24,14 +24,75 @@
 // Note: this class was written without inspecting the non-free org.json sourcecode.
 
 /**
+ * A modifiable set of name/value mappings. Names are unique, non-null strings.
+ * Values may be other {@link JSONObject JSONObjects}, {@link JSONArray
+ * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
+ * Values may not be {@code null}, {@link Double#isNaN() NaNs} or {@link
+ * Double#isInfinite() infinities}.
  *
+ * <p>This class can coerce values to another type when requested.
+ * <ul>
+ *   <li>When the requested type is a boolean, strings will be coerced
+ *       using {@link Boolean#valueOf(String)}.
+ *   <li>When the requested type is a double, other {@link Number} types will
+ *       be coerced using {@link Number#doubleValue() doubleValue()}. Strings
+ *       that can be coerced using {@link Double#valueOf(String)} will be.
+ *   <li>When the requested type is an int, other {@link Number} types will
+ *       be coerced using {@link Number#intValue() intValue()}. Strings
+ *       that can be coerced using {@link Double#valueOf(String)} will be,
+ *       and then cast to int.
+ *   <li>When the requested type is a long, other {@link Number} types will
+ *       be coerced using {@link Number#longValue() longValue()}. Strings
+ *       that can be coerced using {@link Double#valueOf(String)} will be,
+ *       and then cast to long. This two-step conversion is lossy for very
+ *       large values. For example, the string "9223372036854775806" yields the
+ *       long 9223372036854775807.
+ *   <li>When the requested type is a String, other non-null values will be
+ *       coerced using {@link String#valueOf(Object)}.
+ * </ul>
  *
- * <p>TODO: Note about self-use
+ * <p>This class can look up both mandatory and optional values:
+ * <ul>
+ *   <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This
+ *       fails with a {@code JSONException} if the requested name has no value
+ *       or if the value cannot be coerced to the requested type.
+ *   <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This
+ *       returns a system- or user-supplied default if the requested name has no
+ *       value or if the value cannot be coerced to the requested type.
+ * </ul>
+ *
+ * <p><strong>Warning:</strong> this class represents null in two incompatible
+ * ways: the standard Java {@code null} literal, and the sentinel value {@link
+ * JSONObject#NULL JSONObject.NULL}. In particular, calling {@code
+ * put(name, null)} removes the named entry from the object but {@code
+ * put(name, JSONObject.NULL)} stores an entry whose value is {@code
+ * JSONObject.NULL}.
+ *
+ * <p>Instances of this class are not thread safe. Although this class is
+ * nonfinal, it was not designed for inheritance and should not be subclassed.
+ * In particular, self-use by overridable methods is not specified. See
+ * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else
+ * prohibit it" for further information.
  */
 public class JSONObject {
 
     private static final Double NEGATIVE_ZERO = -0d;
 
+    /**
+     * A sentinel value used to explicitly define a name with no value. Unlike
+     * {@code null}, names with this value:
+     * <ul>
+     *   <li>show up in the {@link #names()} array
+     *   <li>show up in the {@link #keys()} iterator
+     *   <li>return {@code true} for {@link #has(String)}
+     *   <li>do not throw on {@link #get(String)}
+     *   <li>are included in the encoded JSON string.
+     * </ul>
+     *
+     * <p>This value violates the general contract of {@link Object#equals} by
+     * returning true when compared to {@code null}. Its {@link #toString}
+     * method returns "null".
+     */
     public static final Object NULL = new Object() {
         @Override public boolean equals(Object o) {
             return o == this || o == null; // API specifies this broken equals implementation
@@ -43,11 +104,22 @@
 
     private final Map<String, Object> nameValuePairs;
 
+    /**
+     * Creates a JSONObject with no name/value mappings.
+     */
     public JSONObject() {
         nameValuePairs = new HashMap<String, Object>();
     }
 
-    /* Accept a raw type for API compatibility */
+    /**
+     * Creates a new JSONObject by copying all name/value mappings from the
+     * given map.
+     *
+     * @param copyFrom a map whose keys are of type {@link String} and whose
+     *     values are of supported types. Values do not need to be homogeneous.
+     * @throws NullPointerException if any of the map's keys are null.
+     */
+    /* (accept a raw type for API compatibility) */
     public JSONObject(Map copyFrom) {
         this();
         Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
@@ -64,6 +136,13 @@
         }
     }
 
+    /**
+     * Creates a new JSONObject with name/value mappings from the next value in
+     * the tokenizer.
+     *
+     * @param readFrom a tokenizer whose nextValue() method will yield a JSONObject.
+     * @throws JSONException if the parse fails or doesn't yield a JSONObject.
+     */
     public JSONObject(JSONTokener readFrom) throws JSONException {
         /*
          * Getting the parser to populate this could get tricky. Instead, just
@@ -77,10 +156,21 @@
         }
     }
 
+    /**
+     * Creates a new JSONObject with name/value mappings from the JSON string.
+     *
+     * @param json a JSON-encoded string containing an object.
+     * @throws JSONException if the parse fails or doesn't yield a JSONObject.
+     */
     public JSONObject(String json) throws JSONException {
         this(new JSONTokener(json));
     }
 
+    /**
+     * Creates a new JSONObject by copying mappings for the listed names from
+     * the given object. Names that aren't present in {@code copyFrom} will be
+     * skipped.
+     */
     public JSONObject(JSONObject copyFrom, String[] names) throws JSONException {
         this();
         for (String name : names) {
@@ -91,30 +181,70 @@
         }
     }
 
+    /**
+     * Returns the number of name/value mappings in this object.
+     */
     public int length() {
         return nameValuePairs.size();
     }
 
+    /**
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
+     *
+     * @return this object.
+     */
     public JSONObject put(String name, boolean value) throws JSONException {
         nameValuePairs.put(checkName(name), value);
         return this;
     }
 
+    /**
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this object.
+     */
     public JSONObject put(String name, double value) throws JSONException {
         nameValuePairs.put(checkName(name), JSON.checkDouble(value));
         return this;
     }
 
+    /**
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
+     *
+     * @return this object.
+     */
     public JSONObject put(String name, int value) throws JSONException {
         nameValuePairs.put(checkName(name), value);
         return this;
     }
 
+    /**
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
+     *
+     * @return this object.
+     */
     public JSONObject put(String name, long value) throws JSONException {
         nameValuePairs.put(checkName(name), value);
         return this;
     }
 
+    /**
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name. If the value is {@code null}, any existing
+     * mapping for {@code name} is removed.
+     *
+     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double, {@link #NULL}, or {@code null}. May not be
+     *     {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+     *     infinities}.
+     * @return this object.
+     */
     public JSONObject put(String name, Object value) throws JSONException {
         if (value == null) {
             nameValuePairs.remove(name);
@@ -128,6 +258,10 @@
         return this;
     }
 
+    /**
+     * Equivalent to {@code put(name, value)} when both parameters are non-null;
+     * does nothing otherwise.
+     */
     public JSONObject putOpt(String name, Object value) throws JSONException {
         if (name == null || value == null) {
             return this;
@@ -135,17 +269,36 @@
         return put(name, value);
     }
 
+    /**
+     * Appends {@code value} to the array already mapped to {@code name}. If
+     * this object has no mapping for {@code name}, this inserts a new mapping.
+     * If the mapping exists but its value is not an array, the existing
+     * and new values are inserted in order into a new array which is itself
+     * mapped to {@code name}. In aggregate, this allows values to be added to a
+     * mapping one at a time.
+     *
+     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double, {@link #NULL} or null. May not be {@link
+     *     Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
+     */
     public JSONObject accumulate(String name, Object value) throws JSONException {
         Object current = nameValuePairs.get(checkName(name));
         if (current == null) {
-            put(name, value);
-        } else if (current instanceof JSONArray) {
+            return put(name, value);
+        }
+
+        // check in accumulate, since array.put(Object) doesn't do any checking
+        if (value instanceof Number) {
+            JSON.checkDouble(((Number) value).doubleValue());
+        }
+
+        if (current instanceof JSONArray) {
             JSONArray array = (JSONArray) current;
             array.put(value);
         } else {
             JSONArray array = new JSONArray();
             array.put(current);
-            array.put(value); // fails on bogus values
+            array.put(value);
             nameValuePairs.put(name, array);
         }
         return this;
@@ -158,19 +311,38 @@
         return name;
     }
 
+    /**
+     * Removes the named mapping if it exists; does nothing otherwise.
+     *
+     * @return the value previously mapped by {@code name}, or null if there was
+     *     no such mapping.
+     */
     public Object remove(String name) {
         return nameValuePairs.remove(name);
     }
 
+    /**
+     * Returns true if this object has no mapping for {@code name} or if it has
+     * a mapping whose value is {@link #NULL}.
+     */
     public boolean isNull(String name) {
         Object value = nameValuePairs.get(name);
         return value == null || value == NULL;
     }
 
+    /**
+     * Returns true if this object has a mapping for {@code name}. The mapping
+     * may be {@link #NULL}.
+     */
     public boolean has(String name) {
         return nameValuePairs.containsKey(name);
     }
 
+    /**
+     * Returns the value mapped by {@code name}.
+     *
+     * @throws JSONException if no such mapping exists.
+     */
     public Object get(String name) throws JSONException {
         Object result = nameValuePairs.get(name);
         if (result == null) {
@@ -179,10 +351,21 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name}, or null if no such mapping
+     * exists.
+     */
     public Object opt(String name) {
         return nameValuePairs.get(name);
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a boolean or
+     * can be coerced to a boolean.
+     *
+     * @throws JSONException if the mapping doesn't exist or cannot be coerced
+     *     to a boolean.
+     */
     public boolean getBoolean(String name) throws JSONException {
         Object object = get(name);
         Boolean result = JSON.toBoolean(object);
@@ -192,16 +375,31 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a boolean or
+     * can be coerced to a boolean. Returns false otherwise.
+     */
     public boolean optBoolean(String name) {
         return optBoolean(name, false);
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a boolean or
+     * can be coerced to a boolean. Returns {@code fallback} otherwise.
+     */
     public boolean optBoolean(String name, boolean fallback) {
         Object object = opt(name);
         Boolean result = JSON.toBoolean(object);
         return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a double or
+     * can be coerced to a double.
+     *
+     * @throws JSONException if the mapping doesn't exist or cannot be coerced
+     *     to a double.
+     */
     public double getDouble(String name) throws JSONException {
         Object object = get(name);
         Double result = JSON.toDouble(object);
@@ -211,16 +409,31 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a double or
+     * can be coerced to a double. Returns {@code NaN} otherwise.
+     */
     public double optDouble(String name) {
         return optDouble(name, Double.NaN);
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a double or
+     * can be coerced to a double. Returns {@code fallback} otherwise.
+     */
     public double optDouble(String name, double fallback) {
         Object object = opt(name);
         Double result = JSON.toDouble(object);
         return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is an int or
+     * can be coerced to an int.
+     *
+     * @throws JSONException if the mapping doesn't exist or cannot be coerced
+     *     to an int.
+     */
     public int getInt(String name) throws JSONException {
         Object object = get(name);
         Integer result = JSON.toInteger(object);
@@ -230,16 +443,31 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is an int or
+     * can be coerced to an int. Returns 0 otherwise.
+     */
     public int optInt(String name) {
         return optInt(name, 0);
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is an int or
+     * can be coerced to an int. Returns {@code fallback} otherwise.
+     */
     public int optInt(String name, int fallback) {
         Object object = opt(name);
         Integer result = JSON.toInteger(object);
         return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a long or
+     * can be coerced to a long.
+     *
+     * @throws JSONException if the mapping doesn't exist or cannot be coerced
+     *     to a long.
+     */
     public long getLong(String name) throws JSONException {
         Object object = get(name);
         Long result = JSON.toLong(object);
@@ -249,16 +477,30 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a long or
+     * can be coerced to a long. Returns 0 otherwise.
+     */
     public long optLong(String name) {
         return optLong(name, 0L);
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a long or
+     * can be coerced to a long. Returns {@code fallback} otherwise.
+     */
     public long optLong(String name, long fallback) {
         Object object = opt(name);
         Long result = JSON.toLong(object);
         return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists, coercing it if
+     * necessary.
+     *
+     * @throws JSONException if no such mapping exists.
+     */
     public String getString(String name) throws JSONException {
         Object object = get(name);
         String result = JSON.toString(object);
@@ -268,16 +510,31 @@
         return result;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists, coercing it if
+     * necessary. Returns the empty string if no such mapping exists.
+     */
     public String optString(String name) {
         return optString(name, "");
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists, coercing it if
+     * necessary. Returns {@code fallback} if no such mapping exists.
+     */
     public String optString(String name, String fallback) {
         Object object = opt(name);
         String result = JSON.toString(object);
         return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a {@code
+     * JSONArray}.
+     *
+     * @throws JSONException if the mapping doesn't exist or is not a {@code
+     *     JSONArray}.
+     */
     public JSONArray getJSONArray(String name) throws JSONException {
         Object object = get(name);
         if (object instanceof JSONArray) {
@@ -287,11 +544,22 @@
         }
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a {@code
+     * JSONArray}. Returns null otherwise.
+     */
     public JSONArray optJSONArray(String name) {
         Object object = opt(name);
         return object instanceof JSONArray ? (JSONArray) object : null;
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a {@code
+     * JSONObject}.
+     *
+     * @throws JSONException if the mapping doesn't exist or is not a {@code
+     *     JSONObject}.
+     */
     public JSONObject getJSONObject(String name) throws JSONException {
         Object object = get(name);
         if (object instanceof JSONObject) {
@@ -301,11 +569,20 @@
         }
     }
 
+    /**
+     * Returns the value mapped by {@code name} if it exists and is a {@code
+     * JSONObject}. Returns null otherwise.
+     */
     public JSONObject optJSONObject(String name) {
         Object object = opt(name);
         return object instanceof JSONObject ? (JSONObject) object : null;
     }
 
+    /**
+     * Returns an array with the values corresponding to {@code names}. The
+     * array contains null for names that aren't mapped. This method returns
+     * null if {@code names} is either null or empty.
+     */
     public JSONArray toJSONArray(JSONArray names) throws JSONException {
         JSONArray result = new JSONArray();
         if (names == null) {
@@ -322,17 +599,32 @@
         return result;
     }
 
+    /**
+     * Returns an iterator of the {@code String} names in this object. The
+     * returned iterator supports {@link Iterator#remove() remove()}, which will
+     * remove the corresponding mapping from this object. If this object is
+     * modified after the iterator is returned, the iterator's behavior is
+     * undefined. The order of the keys is undefined.
+     */
     /* Return a raw type for API compatibility */
     public Iterator keys() {
         return nameValuePairs.keySet().iterator();
     }
 
+    /**
+     * Returns an array containing the string names in this object. This method
+     * returns null if this object contains no mappings.
+     */
     public JSONArray names() {
         return nameValuePairs.isEmpty()
                 ? null
                 : new JSONArray(new ArrayList<String>(nameValuePairs.keySet()));
     }
 
+    /**
+     * Encodes this object as a compact JSON string, such as
+     * <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
+     */
     @Override public String toString() {
         try {
             JSONStringer stringer = new JSONStringer();
@@ -343,6 +635,21 @@
         }
     }
 
+    /**
+     * Encodes this object as a human readable JSON string for debugging, such
+     * as
+     * <pre>
+     * {
+     *     "query": "Pizza",
+     *     "locations": [
+     *         94043,
+     *         90210
+     *     ]
+     * }</pre>
+     *
+     * @param indentSpaces the number of spaces to indent for each level of
+     *     nesting.
+     */
     public String toString(int indentSpaces) throws JSONException {
         JSONStringer stringer = new JSONStringer(indentSpaces);
         writeTo(stringer);
@@ -357,6 +664,12 @@
         stringer.endObject();
     }
 
+    /**
+     * Encodes the number as a JSON string.
+     *
+     * @param number a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     */
     public static String numberToString(Number number) throws JSONException {
         if (number == null) {
             throw new JSONException("Number must be non-null");
@@ -378,6 +691,13 @@
         return number.toString();
     }
 
+    /**
+     * Encodes {@code data} as a JSON string. This applies quotes and any
+     * necessary character escaping.
+     *
+     * @param data the string to encode. Null will be interpreted as an empty
+     *     string.
+     */
     public static String quote(String data) {
         if (data == null) {
             return "\"\"";
diff --git a/libcore/json/src/test/java/org/json/JSONObjectTest.java b/libcore/json/src/test/java/org/json/JSONObjectTest.java
index e431096..b896a7f 100644
--- a/libcore/json/src/test/java/org/json/JSONObjectTest.java
+++ b/libcore/json/src/test/java/org/json/JSONObjectTest.java
@@ -453,6 +453,53 @@
         assertEquals(null, object.optJSONObject("foo"));
     }
 
+    public void testNullCoercionToString() throws JSONException {
+        JSONObject object = new JSONObject();
+        object.put("foo", JSONObject.NULL);
+        assertEquals("null", object.getString("foo"));
+    }
+
+    public void testArrayCoercion() throws JSONException {
+        JSONObject object = new JSONObject();
+        object.put("foo", "[true]");
+        try {
+            object.getJSONArray("foo");
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+    
+    public void testObjectCoercion() throws JSONException {
+        JSONObject object = new JSONObject();
+        object.put("foo", "{}");
+        try {
+            object.getJSONObject("foo");
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testAccumulateValueChecking() throws JSONException {
+        JSONObject object = new JSONObject();
+        try {
+            object.accumulate("foo", Double.NaN);
+            fail();
+        } catch (JSONException e) {
+        }
+        object.accumulate("foo", 1);
+        try {
+            object.accumulate("foo", Double.NaN);
+            fail();
+        } catch (JSONException e) {
+        }
+        object.accumulate("foo", 2);
+        try {
+            object.accumulate("foo", Double.NaN);
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
     public void testToJSONArray() throws JSONException {
         JSONObject object = new JSONObject();
         Object value = new Object();