Merge "include emma into core library on conditional flag"
diff --git a/NOTICE b/NOTICE
index f51da45..39a4ea2 100644
--- a/NOTICE
+++ b/NOTICE
@@ -93,34 +93,6 @@
 
 
    =========================================================================
-   ==  NOTICE file for the JSON License.                                  ==
-   =========================================================================
-
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-
-   =========================================================================
    ==  NOTICE file for the JUnit License.                                 ==
    =========================================================================
 
diff --git a/archive/src/main/java/java/util/zip/InflaterInputStream.java b/archive/src/main/java/java/util/zip/InflaterInputStream.java
index d239a8a..8cd8cf2 100644
--- a/archive/src/main/java/java/util/zip/InflaterInputStream.java
+++ b/archive/src/main/java/java/util/zip/InflaterInputStream.java
@@ -279,8 +279,7 @@
      * @return 0 if no further bytes are available. Otherwise returns 1,
      *         which suggests (but does not guarantee) that additional bytes are
      *         available.
-     * @throws IOException
-     *             If an error occurs.
+     * @throws IOException if this stream is closed or an error occurs
      */
     @Override
     public int available() throws IOException {
diff --git a/archive/src/main/java/java/util/zip/ZipInputStream.java b/archive/src/main/java/java/util/zip/ZipInputStream.java
index 554f5d5..e547612 100644
--- a/archive/src/main/java/java/util/zip/ZipInputStream.java
+++ b/archive/src/main/java/java/util/zip/ZipInputStream.java
@@ -385,18 +385,12 @@
         return skipped;
     }
 
-    /**
-     * Returns 0 if the {@code EOF} has been reached, otherwise returns 1.
-     *
-     * @return 0 after {@code EOF} of current entry, 1 otherwise.
-     * @throws IOException
-     *             if an IOException occurs.
-     */
     @Override
     public int available() throws IOException {
         if (closed) {
             throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
         }
+        // The InflaterInputStream contract says we must only return 0 or 1.
         return (currentEntry == null || inRead < currentEntry.size) ? 1 : 0;
     }
 
diff --git a/concurrent/src/test/java/tests/api/java/util/concurrent/JSR166TestCase.java b/concurrent/src/test/java/tests/api/java/util/concurrent/JSR166TestCase.java
index c900616..1c872f1 100644
--- a/concurrent/src/test/java/tests/api/java/util/concurrent/JSR166TestCase.java
+++ b/concurrent/src/test/java/tests/api/java/util/concurrent/JSR166TestCase.java
@@ -168,7 +168,10 @@
      * be reimplemented to use for example a Property.
      */
     protected long getShortDelay() {
-        return 50;
+        // BEGIN android-changed
+        // original value is 50
+        return 250;
+        // END android-changed
     }
 
 
diff --git a/crypto/src/main/java/javax/crypto/CipherInputStream.java b/crypto/src/main/java/javax/crypto/CipherInputStream.java
index b2c626d..fb1ef27 100644
--- a/crypto/src/main/java/javax/crypto/CipherInputStream.java
+++ b/crypto/src/main/java/javax/crypto/CipherInputStream.java
@@ -185,13 +185,6 @@
         return i;
     }
 
-    /**
-     * Returns the number of bytes available without blocking.
-     *
-     * @return the number of bytes available, currently zero.
-     * @throws IOException
-     *             if an error occurs
-     */
     @Override
     public int available() throws IOException {
         return 0;
@@ -227,4 +220,3 @@
         return false;
     }
 }
-
diff --git a/dalvik/src/main/java/dalvik/system/VMDebug.java b/dalvik/src/main/java/dalvik/system/VMDebug.java
index 6f64c5f..31e82ec 100644
--- a/dalvik/src/main/java/dalvik/system/VMDebug.java
+++ b/dalvik/src/main/java/dalvik/system/VMDebug.java
@@ -359,6 +359,14 @@
      */
     public static native void crash();
 
+    /**
+     * Together with gdb, provide a handy way to stop the VM at user-tagged
+     * locations.
+     *
+     * @hide
+     */
+    public static native void infopoint(int id);
+
     /*
      * Fake method, inserted into dmtrace output when the garbage collector
      * runs.  Not actually called.
diff --git a/dom/src/test/java/org/w3c/domts/level2/core/documentcreateattributeNS04.java b/dom/src/test/java/org/w3c/domts/level2/core/documentcreateattributeNS04.java
index bc7b323..bae9800 100644
--- a/dom/src/test/java/org/w3c/domts/level2/core/documentcreateattributeNS04.java
+++ b/dom/src/test/java/org/w3c/domts/level2/core/documentcreateattributeNS04.java
@@ -92,13 +92,14 @@
           qualifiedName = (String) qualifiedNames.get(indexN1004E);
     
       {
-         boolean success = false;
+          // BEGIN android-changed
+          //     Our exception priorities differ from the spec
          try {
             attribute = doc.createAttributeNS(namespaceURI, qualifiedName);
-          } catch (DOMException ex) {
-            success = (ex.code == DOMException.NAMESPACE_ERR);
+             fail("documentcreateattributeNS04");
+          } catch (DOMException expected) {
          }
-         assertTrue("documentcreateattributeNS04", success);
+          // END android-changed
       }
   }
       }
diff --git a/dom/src/test/java/org/w3c/domts/level2/core/setAttributeNS02.java b/dom/src/test/java/org/w3c/domts/level2/core/setAttributeNS02.java
index 7300f80..9a83561 100644
--- a/dom/src/test/java/org/w3c/domts/level2/core/setAttributeNS02.java
+++ b/dom/src/test/java/org/w3c/domts/level2/core/setAttributeNS02.java
@@ -75,13 +75,14 @@
       testAddr = elementList.item(0);
       
       {
-         boolean success = false;
+          // BEGIN android-changed
+          //     Our exception priorities differ from the spec
          try {
             ((Element) /*Node */testAddr).setAttributeNS(namespaceURI, qualifiedName, "newValue");
+             fail("throw_NAMESPACE_ERR");
           } catch (DOMException ex) {
-            success = (ex.code == DOMException.NAMESPACE_ERR);
          }
-         assertTrue("throw_NAMESPACE_ERR", success);
+          // END android-changed
       }
 }
    /**
diff --git a/json/src/main/java/org/json/JSON.java b/json/src/main/java/org/json/JSON.java
new file mode 100644
index 0000000..b32124d
--- /dev/null
+++ b/json/src/main/java/org/json/JSON.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.json;
+
+class JSON {
+    /**
+     * Returns the input if it is a JSON-permissable value; throws otherwise.
+     */
+    static double checkDouble(double d) throws JSONException {
+        if (Double.isInfinite(d) || Double.isNaN(d)) {
+            throw new JSONException("Forbidden numeric value: " + d);
+        }
+        return d;
+    }
+
+    static Boolean toBoolean(Object value) {
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        } else if (value instanceof String) {
+            return Boolean.valueOf(((String) value));
+        } else {
+            return null;
+        }
+    }
+
+    static Double toDouble(Object value) {
+        if (value instanceof Double) {
+            return (Double) value;
+        } else if (value instanceof Number) {
+            return ((Number) value).doubleValue();
+        } else if (value instanceof String) {
+            try {
+                return Double.valueOf((String) value);
+            } catch (NumberFormatException e) {
+            }
+        }
+        return null;
+    }
+
+    static Integer toInteger(Object value) {
+        if (value instanceof Integer) {
+            return (Integer) value;
+        } else if (value instanceof Number) {
+            return ((Number) value).intValue();
+        } else if (value instanceof String) {
+            try {
+                return (int) Double.parseDouble((String) value);
+            } catch (NumberFormatException e) {
+            }
+        }
+        return null;
+    }
+
+    static Long toLong(Object value) {
+        if (value instanceof Long) {
+            return (Long) value;
+        } else if (value instanceof Number) {
+            return ((Number) value).longValue();
+        } else if (value instanceof String) {
+            try {
+                return (long) Double.parseDouble((String) value);
+            } catch (NumberFormatException e) {
+            }
+        }
+        return null;
+    }
+
+    static String toString(Object value) {
+        if (value instanceof String) {
+            return (String) value;
+        } else if (value != null) {
+            return String.valueOf(value);
+        }
+        return null;
+    }
+
+    public static JSONException typeMismatch(Object indexOrName, Object actual,
+            String requiredType) throws JSONException {
+        if (actual == null) {
+            throw new JSONException("Value at " + indexOrName + " is null.");
+        } else {
+            throw new JSONException("Value " + actual + " at " + indexOrName
+                    + " of type " + actual.getClass().getName()
+                    + " cannot be converted to " + requiredType);
+        }
+    }
+
+    public static JSONException typeMismatch(Object actual, String requiredType)
+            throws JSONException {
+        if (actual == null) {
+            throw new JSONException("Value is null.");
+        } else {
+            throw new JSONException("Value " + actual
+                    + " of type " + actual.getClass().getName()
+                    + " cannot be converted to " + requiredType);
+        }
+    }
+}
diff --git a/json/src/main/java/org/json/JSONArray.java b/json/src/main/java/org/json/JSONArray.java
index 6bfca6e..09d9725 100644
--- a/json/src/main/java/org/json/JSONArray.java
+++ b/json/src/main/java/org/json/JSONArray.java
@@ -1,778 +1,585 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.json;
 
-/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
+import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
 
-/**
- * A JSONArray is an ordered sequence of values. Its external text form is a 
- * string wrapped in square brackets with commas separating the values. The 
- * internal form is an object having <code>get</code> and <code>opt</code> 
- * methods for accessing the values by index, and <code>put</code> methods for 
- * adding or replacing values. The values can be any of these types: 
- * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, 
- * <code>Number</code>, <code>String</code>, or the
- * <code>JSONObject.NULL object</code>.
- * <p>
- * The constructor can convert a JSON text into a Java object. The 
- * <code>toString</code> method converts to JSON text.
- * <p>
- * A <code>get</code> method returns a value if one can be found, and throws an 
- * exception if one cannot be found. An <code>opt</code> method returns a 
- * default value instead of throwing an exception, and so is useful for 
- * obtaining optional values.
- * <p>
- * The generic <code>get()</code> and <code>opt()</code> methods return an 
- * object which you can cast or query for type. There are also typed 
- * <code>get</code> and <code>opt</code> methods that do type checking and type 
- * coersion for you.
- * <p>
- * The texts produced by the <code>toString</code> methods strictly conform to
- * JSON syntax rules. The constructors are more forgiving in the texts they will 
- * accept:
- * <ul>
- * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
- *     before the closing bracket.</li>
- * <li>The <code>null</code> value will be inserted when there
- *     is <code>,</code>&nbsp;<small>(comma)</small> elision.</li>
- * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
- *     quote)</small>.</li>
- * <li>Strings do not need to be quoted at all if they do not begin with a quote
- *     or single quote, and if they do not contain leading or trailing spaces,
- *     and if they do not contain any of these characters:
- *     <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
- *     and if they are not the reserved words <code>true</code>,
- *     <code>false</code>, or <code>null</code>.</li>
- * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as 
- *     well as by <code>,</code> <small>(comma)</small>.</li>
- * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
- *     <code>0x-</code> <small>(hex)</small> prefix.</li>
- * <li>Comments written in the slashshlash, slashstar, and hash conventions
- *     will be ignored.</li>
- * </ul>
+// Note: this class was written without inspecting the non-free org.json sourcecode.
 
- * @author JSON.org
- * @version 2
+/**
+ * A dense indexed sequence of values. Values may be any mix of
+ * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings,
+ * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}.
+ * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite()
+ * infinities}, or of any type not listed here.
+ *
+ * <p>{@code JSONArray} has the same type coercion behavior and
+ * optional/mandatory accessors as {@link JSONObject}. See that class'
+ * documentation for details.
+ *
+ * <p><strong>Warning:</strong> this class represents null in two incompatible
+ * ways: the standard Java {@code null} reference, and the sentinel value {@link
+ * JSONObject#NULL}. In particular, {@code get} fails if the requested index
+ * holds the null reference, but succeeds if it holds {@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 JSONArray {
 
+    private final List<Object> values;
 
     /**
-     * The arrayList where the JSONArray's properties are kept.
-     */
-    private ArrayList myArrayList;
-
-
-    /**
-     * Construct an empty JSONArray.
+     * Creates a {@code JSONArray} with no values.
      */
     public JSONArray() {
-        this.myArrayList = new ArrayList();
+        values = new ArrayList<Object>();
     }
-    
-    /**
-     * Construct a JSONArray from a JSONTokener.
-     * @param x A JSONTokener
-     * @throws JSONException If there is a syntax error.
-     */
-    public JSONArray(JSONTokener x) throws JSONException {
-        this();
-        if (x.nextClean() != '[') {
-            throw x.syntaxError("A JSONArray text must start with '['");
-        }
-        if (x.nextClean() == ']') {
-            return;
-        }
-        x.back();
-        for (;;) {
-            if (x.nextClean() == ',') {
-                x.back();
-                this.myArrayList.add(null);
-            } else {
-                x.back();
-                this.myArrayList.add(x.nextValue());
-            }
-            switch (x.nextClean()) {
-            case ';':
-            case ',':
-                if (x.nextClean() == ']') {
-                    this.myArrayList.add(null);
-                    return;
-                }
-                x.back();
-                break;
-            case ']':
-                return;
-            default:
-                throw x.syntaxError("Expected a ',' or ']'");
-            }
-        }
-    }
-
-
-    public boolean equals(Object object) {
-        if (!(object instanceof JSONArray)) return false;
-        return myArrayList.equals(((JSONArray)object).myArrayList);
-    }
-
-    
-    /**
-     * Construct a JSONArray from a source sJSON text.
-     * @param string     A string that begins with
-     * <code>[</code>&nbsp;<small>(left bracket)</small>
-     *  and ends with <code>]</code>&nbsp;<small>(right bracket)</small>.
-     *  @throws JSONException If there is a syntax error.
-     */
-    public JSONArray(String string) throws JSONException {
-        this(new JSONTokener(string));
-    }
-
 
     /**
-     * Construct a JSONArray from a Collection.
-     * @param collection     A Collection.
-     */
-    public JSONArray(Collection collection) {
-        this.myArrayList = new ArrayList(collection);
-    }
-
-
-    /**
-     * Get the object value associated with an index.
-     * @param index
-     *  The index must be between 0 and length() - 1.
-     * @return An object value.
-     * @throws JSONException If there is no value for the index.
-     */
-    public Object get(int index) throws JSONException {
-        Object o = opt(index);
-        if (o == null) {
-            throw new JSONException("JSONArray[" + index + "] not found.");
-        }
-        return o;
-    }
-
-
-    /**
-     * Get the boolean value associated with an index.
-     * The string values "true" and "false" are converted to boolean.
-     * 
-     * @param index The index must be between 0 and length() - 1.
-     * @return      The truth.
-     * @throws JSONException If there is no value for the index or if the
-     *  value is not convertable to boolean.
-     */
-    public boolean getBoolean(int index) throws JSONException {
-        Object o = get(index);
-        if (o.equals(Boolean.FALSE) ||
-                (o instanceof String &&
-                ((String)o).equalsIgnoreCase("false"))) {
-            return false;
-        } else if (o.equals(Boolean.TRUE) ||
-                (o instanceof String &&
-                ((String)o).equalsIgnoreCase("true"))) {
-            return true;
-        }
-        throw new JSONException("JSONArray[" + index + "] is not a Boolean.");
-    }
-
-
-    /**
-     * Get the double value associated with an index.
-     * 
-     * @param index The index must be between 0 and length() - 1.
-     * @return      The value.
-     * @throws   JSONException If the key is not found or if the value cannot 
-     *  be converted to a number.
-     */
-    public double getDouble(int index) throws JSONException {
-        Object o = get(index);
-        try {
-            return o instanceof Number ? 
-                    ((Number)o).doubleValue() : Double.parseDouble((String)o);
-        } catch (Exception e) {
-            throw new JSONException("JSONArray[" + index + 
-                "] is not a number.");
-        }
-    }
-
-
-    /**
-     * Get the int value associated with an index.
-     * 
-     * @param index The index must be between 0 and length() - 1.
-     * @return      The value.
-     * @throws   JSONException If the key is not found or if the value cannot 
-     *  be converted to a number.
-     *  if the value cannot be converted to a number.
-     */
-    public int getInt(int index) throws JSONException {
-        Object o = get(index);
-        return o instanceof Number ? 
-                ((Number)o).intValue() : (int)getDouble(index);
-    }
-
-
-    /**
-     * Get the JSONArray associated with an index.
-     * @param index The index must be between 0 and length() - 1.
-     * @return      A JSONArray value.
-     * @throws JSONException If there is no value for the index. or if the
-     * value is not a JSONArray
-     */
-    public JSONArray getJSONArray(int index) throws JSONException {
-        Object o = get(index);
-        if (o instanceof JSONArray) {
-            return (JSONArray)o;
-        }
-        throw new JSONException("JSONArray[" + index +
-                "] is not a JSONArray.");
-    }
-
-
-    /**
-     * Get the JSONObject associated with an index.
-     * @param index subscript
-     * @return      A JSONObject value.
-     * @throws JSONException If there is no value for the index or if the
-     * value is not a JSONObject
-     */
-    public JSONObject getJSONObject(int index) throws JSONException {
-        Object o = get(index);
-        if (o instanceof JSONObject) {
-            return (JSONObject)o;
-        }
-        throw new JSONException("JSONArray[" + index +
-            "] is not a JSONObject.");
-    }
-
-
-    /**
-     * Get the long value associated with an index.
-     * 
-     * @param index The index must be between 0 and length() - 1.
-     * @return      The value.
-     * @throws   JSONException If the key is not found or if the value cannot 
-     *  be converted to a number.
-     */
-    public long getLong(int index) throws JSONException {
-        Object o = get(index);
-        return o instanceof Number ? 
-                ((Number)o).longValue() : (long)getDouble(index);
-    }
-
-
-    /**
-     * Get the string associated with an index.
-     * @param index The index must be between 0 and length() - 1.
-     * @return      A string value.
-     * @throws JSONException If there is no value for the index.
-     */
-    public String getString(int index) throws JSONException {
-        return get(index).toString();
-    }
-
-
-    /**
-     * Determine if the value is null.
-     * @param index The index must be between 0 and length() - 1.
-     * @return true if the value at the index is null, or if there is no value.
-     */
-    public boolean isNull(int index) {
-        return JSONObject.NULL.equals(opt(index));
-    }
-
-
-    /**
-     * Make a string from the contents of this JSONArray. The 
-     * <code>separator</code> string is inserted between each element.
-     * Warning: This method assumes that the data structure is acyclical.
-     * @param separator A string that will be inserted between the elements.
-     * @return a string.
-     * @throws JSONException If the array contains an invalid number.
-     */
-    public String join(String separator) throws JSONException {
-        int len = length();
-        StringBuilder sb = new StringBuilder();
-        
-        for (int i = 0; i < len; i += 1) {
-            if (i > 0) {
-                sb.append(separator);
-            }
-            sb.append(JSONObject.valueToString(this.myArrayList.get(i)));
-        }
-        return sb.toString();
-    }
-
-
-    /**
-     * Get the number of elements in the JSONArray, included nulls.
+     * Creates a new {@code JSONArray} by copying all values from the given
+     * collection.
      *
-     * @return The length (or size).
+     * @param copyFrom a collection whose values are of supported types.
+     *     Unsupported values are not permitted and will yield an array in an
+     *     inconsistent state.
+     */
+    /* Accept a raw type for API compatibility */
+    public JSONArray(Collection copyFrom) {
+        this();
+        Collection<?> copyFromTyped = (Collection<?>) copyFrom;
+        values.addAll(copyFromTyped);
+    }
+
+    /**
+     * Creates a new {@code JSONArray} with values from the next array in the
+     * tokener.
+     *
+     * @param readFrom a tokener whose nextValue() method will yield a
+     *     {@code JSONArray}.
+     * @throws JSONException if the parse fails or doesn't yield a
+     *     {@code JSONArray}.
+     */
+    public JSONArray(JSONTokener readFrom) throws JSONException {
+        /*
+         * Getting the parser to populate this could get tricky. Instead, just
+         * parse to temporary JSONArray and then steal the data from that.
+         */
+        Object object = readFrom.nextValue();
+        if (object instanceof JSONArray) {
+            values = ((JSONArray) object).values;
+        } else {
+            throw JSON.typeMismatch(object, "JSONArray");
+        }
+    }
+
+    /**
+     * Creates a new {@code JSONArray} with values from the JSON string.
+     *
+     * @param json a JSON-encoded string containing an array.
+     * @throws JSONException if the parse fails or doesn't yield a {@code
+     *     JSONArray}.
+     */
+    public JSONArray(String json) throws JSONException {
+        this(new JSONTokener(json));
+    }
+
+    /**
+     * Returns the number of values in this array.
      */
     public int length() {
-        return this.myArrayList.size();
+        return values.size();
     }
 
+    /**
+     * Appends {@code value} to the end of this array.
+     *
+     * @return this array.
+     */
+    public JSONArray put(boolean value) {
+        values.add(value);
+        return this;
+    }
 
     /**
-     * Get the optional object value associated with an index.
-     * @param index The index must be between 0 and length() - 1.
-     * @return      An object value, or null if there is no
-     *              object at that index.
+     * Appends {@code value} to the end of this array.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this array.
+     */
+    public JSONArray put(double value) throws JSONException {
+        values.add(JSON.checkDouble(value));
+        return this;
+    }
+
+    /**
+     * Appends {@code value} to the end of this array.
+     *
+     * @return this array.
+     */
+    public JSONArray put(int value) {
+        values.add(value);
+        return this;
+    }
+
+    /**
+     * Appends {@code value} to the end of this array.
+     *
+     * @return this array.
+     */
+    public JSONArray put(long value) {
+        values.add(value);
+        return this;
+    }
+
+    /**
+     * Appends {@code value} to the end of this array.
+     *
+     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
+     *     not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+     *     infinities}. Unsupported values are not permitted and will cause the
+     *     array to be in an inconsistent state.
+     * @return this array.
+     */
+    public JSONArray put(Object value) {
+        values.add(value);
+        return this;
+    }
+
+    /**
+     * Sets the value at {@code index} to {@code value}, null padding this array
+     * to the required length if necessary. If a value already exists at {@code
+     * index}, it will be replaced.
+     *
+     * @return this array.
+     */
+    public JSONArray put(int index, boolean value) throws JSONException {
+        return put(index, (Boolean) value);
+    }
+
+    /**
+     * Sets the value at {@code index} to {@code value}, null padding this array
+     * to the required length if necessary. If a value already exists at {@code
+     * index}, it will be replaced.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this array.
+     */
+    public JSONArray put(int index, double value) throws JSONException {
+        return put(index, (Double) value);
+    }
+
+    /**
+     * Sets the value at {@code index} to {@code value}, null padding this array
+     * to the required length if necessary. If a value already exists at {@code
+     * index}, it will be replaced.
+     *
+     * @return this array.
+     */
+    public JSONArray put(int index, int value) throws JSONException {
+        return put(index, (Integer) value);
+    }
+
+    /**
+     * Sets the value at {@code index} to {@code value}, null padding this array
+     * to the required length if necessary. If a value already exists at {@code
+     * index}, it will be replaced.
+     *
+     * @return this array.
+     */
+    public JSONArray put(int index, long value) throws JSONException {
+        return put(index, (Long) value);
+    }
+
+    /**
+     * Sets the value at {@code index} to {@code value}, null padding this array
+     * to the required length if necessary. If a value already exists at {@code
+     * index}, it will be replaced.
+     *
+     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
+     *     not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+     *     infinities}.
+     * @return this array.
+     */
+    public JSONArray put(int index, Object value) throws JSONException {
+        if (value instanceof Number) {
+            // deviate from the original by checking all Numbers, not just floats & doubles
+            JSON.checkDouble(((Number) value).doubleValue());
+        }
+        while (values.size() <= index) {
+            values.add(null);
+        }
+        values.set(index, value);
+        return this;
+    }
+
+    /**
+     * Returns true if this array has no value at {@code index}, or if its value
+     * is the {@code null} reference or {@link JSONObject#NULL}.
+     */
+    public boolean isNull(int index) {
+        Object value = opt(index);
+        return value == null || value == JSONObject.NULL;
+    }
+
+    /**
+     * Returns the value at {@code index}.
+     *
+     * @throws JSONException if this array has no value at {@code index}, or if
+     *     that value is the {@code null} reference. This method returns
+     *     normally if the value is {@code JSONObject#NULL}.
+     */
+    public Object get(int index) throws JSONException {
+        try {
+            Object value = values.get(index);
+            if (value == null) {
+                throw new JSONException("Value at " + index + " is null.");
+            }
+            return value;
+        } catch (IndexOutOfBoundsException e) {
+            throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")");
+        }
+    }
+
+    /**
+     * Returns the value at {@code index}, or null if the array has no value
+     * at {@code index}.
      */
     public Object opt(int index) {
-        return (index < 0 || index >= length()) ? 
-            null : this.myArrayList.get(index);
+        if (index < 0 || index >= values.size()) {
+            return null;
+        }
+        return values.get(index);
     }
 
+    /**
+     * Returns the value at {@code index} if it exists and is a boolean or can
+     * be coerced to a boolean.
+     *
+     * @throws JSONException if the value at {@code index} doesn't exist or
+     *     cannot be coerced to a boolean.
+     */
+    public boolean getBoolean(int index) throws JSONException {
+        Object object = get(index);
+        Boolean result = JSON.toBoolean(object);
+        if (result == null) {
+            throw JSON.typeMismatch(index, object, "boolean");
+        }
+        return result;
+    }
 
     /**
-     * Get the optional boolean value associated with an index.
-     * It returns false if there is no value at that index,
-     * or if the value is not Boolean.TRUE or the String "true".
-     *
-     * @param index The index must be between 0 and length() - 1.
-     * @return      The truth.
+     * Returns the value at {@code index} if it exists and is a boolean or can
+     * be coerced to a boolean. Returns false otherwise.
      */
-    public boolean optBoolean(int index)  {
+    public boolean optBoolean(int index) {
         return optBoolean(index, false);
     }
 
-
     /**
-     * Get the optional boolean value associated with an index.
-     * It returns the defaultValue if there is no value at that index or if 
-     * it is not a Boolean or the String "true" or "false" (case insensitive).
-     *
-     * @param index The index must be between 0 and length() - 1.
-     * @param defaultValue     A boolean default.
-     * @return      The truth.
+     * Returns the value at {@code index} if it exists and is a boolean or can
+     * be coerced to a boolean. Returns {@code fallback} otherwise.
      */
-    public boolean optBoolean(int index, boolean defaultValue)  {
-        try {
-            return getBoolean(index);
-        } catch (Exception e) {
-            return defaultValue;
-        }
+    public boolean optBoolean(int index, boolean fallback) {
+        Object object = opt(index);
+        Boolean result = JSON.toBoolean(object);
+        return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value at {@code index} if it exists and is a double or can
+     * be coerced to a double.
+     *
+     * @throws JSONException if the value at {@code index} doesn't exist or
+     *     cannot be coerced to a double.
+     */
+    public double getDouble(int index) throws JSONException {
+        Object object = get(index);
+        Double result = JSON.toDouble(object);
+        if (result == null) {
+            throw JSON.typeMismatch(index, object, "double");
+        }
+        return result;
+    }
 
     /**
-     * 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 index The index must be between 0 and length() - 1.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is a double or can
+     * be coerced to a double. Returns {@code NaN} otherwise.
      */
     public double optDouble(int index) {
         return optDouble(index, Double.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 index subscript
-     * @param defaultValue     The default value.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is a double or can
+     * be coerced to a double. Returns {@code fallback} otherwise.
      */
-    public double optDouble(int index, double defaultValue) {
-        try {
-            return getDouble(index);
-        } catch (Exception e) {
-            return defaultValue;
-        }
+    public double optDouble(int index, double fallback) {
+        Object object = opt(index);
+        Double result = JSON.toDouble(object);
+        return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value at {@code index} if it exists and is an int or
+     * can be coerced to an int.
+     *
+     * @throws JSONException if the value at {@code index} doesn't exist or
+     *     cannot be coerced to a int.
+     */
+    public int getInt(int index) throws JSONException {
+        Object object = get(index);
+        Integer result = JSON.toInteger(object);
+        if (result == null) {
+            throw JSON.typeMismatch(index, object, "int");
+        }
+        return result;
+    }
 
     /**
-     * Get the optional int value associated with an index.
-     * Zero 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 index The index must be between 0 and length() - 1.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is an int or
+     * can be coerced to an int. Returns 0 otherwise.
      */
     public int optInt(int index) {
         return optInt(index, 0);
     }
 
+    /**
+     * Returns the value at {@code index} if it exists and is an int or
+     * can be coerced to an int. Returns {@code fallback} otherwise.
+     */
+    public int optInt(int index, int fallback) {
+        Object object = opt(index);
+        Integer result = JSON.toInteger(object);
+        return result != null ? result : fallback;
+    }
 
     /**
-     * Get the optional int 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 index The index must be between 0 and length() - 1.
-     * @param defaultValue     The default value.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is a long or
+     * can be coerced to a long.
+     *
+     * @throws JSONException if the value at {@code index} doesn't exist or
+     *     cannot be coerced to a long.
      */
-    public int optInt(int index, int defaultValue) {
-        try {
-            return getInt(index);
-        } catch (Exception e) {
-            return defaultValue;
+    public long getLong(int index) throws JSONException {
+        Object object = get(index);
+        Long result = JSON.toLong(object);
+        if (result == null) {
+            throw JSON.typeMismatch(index, object, "long");
         }
+        return result;
     }
 
-
     /**
-     * Get the optional JSONArray associated with an index.
-     * @param index subscript
-     * @return      A JSONArray value, or null if the index has no value,
-     * or if the value is not a JSONArray.
-     */
-    public JSONArray optJSONArray(int index) {
-        Object o = opt(index);
-        return o instanceof JSONArray ? (JSONArray)o : null;
-    }
-
-
-    /**
-     * Get the optional JSONObject associated with an index.
-     * Null is returned if the key is not found, or null if the index has
-     * no value, or if the value is not a JSONObject.
-     *
-     * @param index The index must be between 0 and length() - 1.
-     * @return      A JSONObject value.
-     */
-    public JSONObject optJSONObject(int index) {
-        Object o = opt(index);
-        return o instanceof JSONObject ? (JSONObject)o : null;
-    }
-
-
-    /**
-     * Get the optional long value associated with an index.
-     * Zero 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 index The index must be between 0 and length() - 1.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is a long or
+     * can be coerced to a long. Returns 0 otherwise.
      */
     public long optLong(int index) {
-        return optLong(index, 0);
+        return optLong(index, 0L);
     }
 
-
     /**
-     * Get the optional long 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 index The index must be between 0 and length() - 1.
-     * @param defaultValue     The default value.
-     * @return      The value.
+     * Returns the value at {@code index} if it exists and is a long or
+     * can be coerced to a long. Returns {@code fallback} otherwise.
      */
-    public long optLong(int index, long defaultValue) {
-        try {
-            return getLong(index);
-        } catch (Exception e) {
-            return defaultValue;
-        }
+    public long optLong(int index, long fallback) {
+        Object object = opt(index);
+        Long result = JSON.toLong(object);
+        return result != null ? result : fallback;
     }
 
+    /**
+     * Returns the value at {@code index} if it exists, coercing it if
+     * necessary.
+     *
+     * @throws JSONException if no such value exists.
+     */
+    public String getString(int index) throws JSONException {
+        Object object = get(index);
+        String result = JSON.toString(object);
+        if (result == null) {
+            throw JSON.typeMismatch(index, object, "String");
+        }
+        return result;
+    }
 
     /**
-     * Get the optional string value associated with an index. It returns an
-     * empty string if there is no value at that index. If the value
-     * is not a string and is not null, then it is coverted to a string.
-     *
-     * @param index The index must be between 0 and length() - 1.
-     * @return      A String value.
+     * Returns the value at {@code index} if it exists, coercing it if
+     * necessary. Returns the empty string if no such value exists.
      */
     public String optString(int index) {
         return optString(index, "");
     }
 
+    /**
+     * Returns the value at {@code index} if it exists, coercing it if
+     * necessary. Returns {@code fallback} if no such value exists.
+     */
+    public String optString(int index, String fallback) {
+        Object object = opt(index);
+        String result = JSON.toString(object);
+        return result != null ? result : fallback;
+    }
 
     /**
-     * Get the optional string associated with an index.
-     * The defaultValue is returned if the key is not found.
+     * Returns the value at {@code index} if it exists and is a {@code
+     * JSONArray}.
      *
-     * @param index The index must be between 0 and length() - 1.
-     * @param defaultValue     The default value.
-     * @return      A String value.
+     * @throws JSONException if the value doesn't exist or is not a {@code
+     *     JSONArray}.
      */
-    public String optString(int index, String defaultValue) {
-        Object o = opt(index);
-        return o != null ? o.toString() : defaultValue;
-    }
-
-
-    /**
-     * Append a boolean value. This increases the array's length by one.
-     *
-     * @param value A boolean value.
-     * @return this.
-     */
-    public JSONArray put(boolean value) {
-        put(Boolean.valueOf(value));
-        return this;
-    }
-
-
-    /**
-     * Append a double value. This increases the array's length by one.
-     *
-     * @param value A double value.
-     * @throws JSONException if the value is not finite.
-     * @return this.
-     */
-    public JSONArray put(double value) throws JSONException {
-        Double d = new Double(value);
-        JSONObject.testValidity(d);
-        put(d);
-        return this;
-    }
-
-
-    /**
-     * Append an int value. This increases the array's length by one.
-     *
-     * @param value An int value.
-     * @return this.
-     */
-    public JSONArray put(int value) {
-        put(new Integer(value));
-        return this;
-    }
-
-
-    /**
-     * Append an long value. This increases the array's length by one.
-     *
-     * @param value A long value.
-     * @return this.
-     */
-    public JSONArray put(long value) {
-        put(new Long(value));
-        return this;
-    }
-
-
-    /**
-     * Append an object value. This increases the array's length by one.
-     * @param value An object value.  The value should be a
-     *  Boolean, Double, Integer, JSONArray, JSObject, Long, or String, or the
-     *  JSONObject.NULL object.
-     * @return this.
-     */
-    public JSONArray put(Object value) {
-        this.myArrayList.add(value);
-        return this;
-    }
-
-
-    /**
-     * Put or replace a boolean value in the JSONArray. If the index is greater 
-     * than the length of the JSONArray, then null elements will be added as 
-     * necessary to pad it out.
-     * @param index The subscript. 
-     * @param value A boolean value.
-     * @return this.
-     * @throws JSONException If the index is negative.
-     */
-    public JSONArray put(int index, boolean value) throws JSONException {
-        put(index, Boolean.valueOf(value));
-        return this;
-    }
-
-
-    /**
-     * Put or replace a double value. If the index is greater than the length of
-     *  the JSONArray, then null elements will be added as necessary to pad
-     *  it out.
-     * @param index The subscript. 
-     * @param value A double value.
-     * @return this.
-     * @throws JSONException If the index is negative or if the value is
-     * not finite.
-     */
-    public JSONArray put(int index, double value) throws JSONException {
-        put(index, new Double(value));
-        return this;
-    }
-
-
-    /**
-     * Put or replace an int value. If the index is greater than the length of
-     *  the JSONArray, then null elements will be added as necessary to pad
-     *  it out.
-     * @param index The subscript. 
-     * @param value An int value.
-     * @return this.
-     * @throws JSONException If the index is negative.
-     */
-    public JSONArray put(int index, int value) throws JSONException {
-        put(index, new Integer(value));
-        return this;
-    }
-
-
-    /**
-     * Put or replace a long value. If the index is greater than the length of
-     *  the JSONArray, then null elements will be added as necessary to pad
-     *  it out.
-     * @param index The subscript. 
-     * @param value A long value.
-     * @return this.
-     * @throws JSONException If the index is negative.
-     */
-    public JSONArray put(int index, long value) throws JSONException {
-        put(index, new Long(value));
-        return this;
-    }
-
-
-    /**
-     * Put or replace an object value in the JSONArray. If the index is greater 
-     *  than the length of the JSONArray, then null elements will be added as
-     *  necessary to pad it out.
-     * @param index The subscript. 
-     * @param value The value to put into the array.
-     * @return this.
-     * @throws JSONException If the index is negative or if the the value is
-     *     an invalid number.
-     */
-    public JSONArray put(int index, Object value) throws JSONException {
-        JSONObject.testValidity(value);
-        if (index < 0) {
-            throw new JSONException("JSONArray[" + index + "] not found.");
-        } 
-        if (index < length()) {
-            this.myArrayList.set(index, value);
+    public JSONArray getJSONArray(int index) throws JSONException {
+        Object object = get(index);
+        if (object instanceof JSONArray) {
+            return (JSONArray) object;
         } else {
-            while (index != length()) {
-                put(null);
-            }
-            put(value);
+            throw JSON.typeMismatch(index, object, "JSONArray");
         }
-        return this;
     }
 
+    /**
+     * Returns the value at {@code index} if it exists and is a {@code
+     * JSONArray}. Returns null otherwise.
+     */
+    public JSONArray optJSONArray(int index) {
+        Object object = opt(index);
+        return object instanceof JSONArray ? (JSONArray) object : null;
+    }
 
     /**
-     * Produce a JSONObject by combining a JSONArray of names with the values
-     * of this JSONArray.
-     * @param names A JSONArray containing a list of key strings. These will be
-     * paired with the values.
-     * @return A JSONObject, or null if there are no names or if this JSONArray
-     * has no values.
-     * @throws JSONException If any of the names are null.
+     * Returns the value at {@code index} if it exists and is a {@code
+     * JSONObject}.
+     *
+     * @throws JSONException if the value doesn't exist or is not a {@code
+     *     JSONObject}.
+     */
+    public JSONObject getJSONObject(int index) throws JSONException {
+        Object object = get(index);
+        if (object instanceof JSONObject) {
+            return (JSONObject) object;
+        } else {
+            throw JSON.typeMismatch(index, object, "JSONObject");
+        }
+    }
+
+    /**
+     * Returns the value at {@code index} if it exists and is a {@code
+     * JSONObject}. Returns null otherwise.
+     */
+    public JSONObject optJSONObject(int index) {
+        Object object = opt(index);
+        return object instanceof JSONObject ? (JSONObject) object : null;
+    }
+
+    /**
+     * Returns a new object whose values are the values in this array, and whose
+     * names are the values in {@code names}. Names and values are paired up by
+     * index from 0 through to the shorter array's length. Names that are not
+     * strings will be coerced to strings. This method returns null if either
+     * array is empty.
      */
     public JSONObject toJSONObject(JSONArray names) throws JSONException {
-        if (names == null || names.length() == 0 || length() == 0) {
+        JSONObject result = new JSONObject();
+        int length = Math.min(names.length(), values.size());
+        if (length == 0) {
             return null;
         }
-        JSONObject jo = new JSONObject();
-        for (int i = 0; i < names.length(); i += 1) {
-            jo.put(names.getString(i), this.opt(i));
+        for (int i = 0; i < length; i++) {
+            String name = JSON.toString(names.opt(i));
+            result.put(name, opt(i));
         }
-        return jo;
+        return result;
     }
 
+    /**
+     * Returns a new string by alternating this array's values with {@code
+     * separator}. This array's string values are quoted and have their special
+     * characters escaped. For example, the array containing the strings '12"
+     * pizza', 'taco' and 'soda' joined on '+' returns this:
+     * <pre>"12\" pizza"+"taco"+"soda"</pre>
+     */
+    public String join(String separator) throws JSONException {
+        JSONStringer stringer = new JSONStringer();
+        stringer.open(JSONStringer.Scope.NULL, "");
+        for (int i = 0, size = values.size(); i < size; i++) {
+            if (i > 0) {
+                stringer.out.append(separator);
+            }
+            stringer.value(values.get(i));
+        }
+        stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
+        return stringer.out.toString();
+    }
 
     /**
-     * Make an JSON text of this JSONArray. For compactness, no
-     * unnecessary whitespace is added. If it is not possible to produce a
-     * syntactically correct JSON text then null will be returned instead. This
-     * could occur if the array contains an invalid number.
-     * <p>
-     * Warning: This method assumes that the data structure is acyclical.
-     *
-     * @return a printable, displayable, transmittable
-     *  representation of the array.
+     * Encodes this array as a compact JSON string, such as:
+     * <pre>[94043,90210]</pre>
      */
-    public String toString() {
+    @Override public String toString() {
         try {
-            return '[' + join(",") + ']';
-        } catch (Exception e) {
+            JSONStringer stringer = new JSONStringer();
+            writeTo(stringer);
+            return stringer.toString();
+        } catch (JSONException e) {
             return null;
         }
     }
 
-
     /**
-     * Make a prettyprinted JSON text of this JSONArray.
-     * 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, transmittable
-     *  representation of the object, beginning
-     *  with <code>[</code>&nbsp;<small>(left bracket)</small> and ending
-     *  with <code>]</code>&nbsp;<small>(right bracket)</small>.
-     * @throws JSONException
+     * Encodes this array as a human readable JSON string for debugging, such
+     * as:
+     * <pre>
+     * [
+     *     94043,
+     *     90210
+     * ]</pre>
+     *
+     * @param indentSpaces the number of spaces to indent for each level of
+     *     nesting.
      */
-    public String toString(int indentFactor) throws JSONException {
-        return toString(indentFactor, 0);
+    public String toString(int indentSpaces) throws JSONException {
+        JSONStringer stringer = new JSONStringer(indentSpaces);
+        writeTo(stringer);
+        return stringer.toString();
     }
 
+    void writeTo(JSONStringer stringer) throws JSONException {
+        stringer.array();
+        for (Object value : values) {
+            stringer.value(value);
+        }
+        stringer.endArray();
+    }
 
-    /**
-     * Make a prettyprinted JSON text of this JSONArray.
-     * Warning: This method assumes that the data structure is acyclical.
-     * @param indentFactor The number of spaces to add to each level of
-     *  indentation.
-     * @param indent The indention of the top level.
-     * @return a printable, displayable, transmittable
-     *  representation of the array.
-     * @throws JSONException
-     */
-    String toString(int indentFactor, int indent) throws JSONException {
-        int len = length();
-        if (len == 0) {
-            return "[]";
-        }
-        int i;
-        StringBuilder sb = new StringBuilder("[");
-        if (len == 1) {
-            sb.append(JSONObject.valueToString(this.myArrayList.get(0),
-                    indentFactor, indent));
-        } else {
-            int newindent = indent + indentFactor;
-            sb.append('\n');
-            for (i = 0; i < len; i += 1) {
-                if (i > 0) {
-                    sb.append(",\n");
-                }
-                for (int j = 0; j < newindent; j += 1) {
-                    sb.append(' ');
-                }
-                sb.append(JSONObject.valueToString(this.myArrayList.get(i),
-                        indentFactor, newindent));
-            }
-            sb.append('\n');
-            for (i = 0; i < indent; i += 1) {
-                sb.append(' ');
-            }
-        }
-        sb.append(']');
-        return sb.toString();
+    @Override public boolean equals(Object o) {
+        return o instanceof JSONArray && ((JSONArray) o).values.equals(values);
+    }
+
+    @Override public int hashCode() {
+        // diverge from the original, which doesn't implement hashCode
+        return values.hashCode();
     }
 }
diff --git a/json/src/main/java/org/json/JSONException.java b/json/src/main/java/org/json/JSONException.java
index a2905bf..ddd1016 100644
--- a/json/src/main/java/org/json/JSONException.java
+++ b/json/src/main/java/org/json/JSONException.java
@@ -1,16 +1,49 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.json;
 
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
 /**
- * The JSONException is thrown by the JSON.org classes then things are amiss.
- * @author JSON.org
- * @version 2
+ * Thrown to indicate a problem with the JSON API. Such problems include:
+ * <ul>
+ *   <li>Attempts to parse or construct malformed documents
+ *   <li>Use of null as a name
+ *   <li>Use of numeric types not available to JSON, such as {@link
+ *       Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
+ *   <li>Lookups using an out of range index or nonexistant name
+ *   <li>Type mismatches on lookups
+ * </ul>
+ *
+ * <p>Although this is a checked exception, it is rarely recoverable. Most
+ * callers should simply wrap this exception in an unchecked exception and
+ * rethrow:
+ * <pre>  public JSONArray toJSONObject() {
+ *     try {
+ *         JSONObject result = new JSONObject();
+ *         ...
+ *     } catch (JSONException e) {
+ *         throw new RuntimeException(e);
+ *     }
+ * }</pre>
  */
 public class JSONException extends Exception {
-    /**
-     * Constructs a JSONException with an explanatory message.
-     * @param message Detail about the reason for the exception.
-     */
-    public JSONException(String message) {
-        super(message);
+
+    public JSONException(String s) {
+        super(s);
     }
 }
diff --git a/json/src/main/java/org/json/JSONObject.java b/json/src/main/java/org/json/JSONObject.java
index b0ebea2..56c91cf 100644
--- a/json/src/main/java/org/json/JSONObject.java
+++ b/json/src/main/java/org/json/JSONObject.java
@@ -1,1081 +1,720 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.json;
 
-/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
-
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
-// BEGIN android-note
-//   - fixed bad htm in javadoc comments -joeo
-// END android-note
+// Note: this class was written without inspecting the non-free org.json sourcecode.
 
 /**
- * A JSONObject is an unordered collection of name/value pairs. Its
- * external form is a string wrapped in curly braces with colons between the
- * names and values, and commas between the values and names. The internal form
- * is an object having <code>get</code> and <code>opt</code> methods for 
- * accessing the values by name, and <code>put</code> methods for adding or 
- * replacing values by name. The values can be any of these types: 
- * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>, 
- * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL</code> 
- * object. A JSONObject constructor can be used to convert an external form
- * JSON text into an internal form whose values can be retrieved with the 
- * <code>get</code> and <code>opt</code> methods, or to convert values into a 
- * JSON text using the <code>put</code> and <code>toString</code> methods.
- * A <code>get</code> method returns a value if one can be found, and throws an 
- * exception if one cannot be found. An <code>opt</code> method returns a 
- * default value instead of throwing an exception, and so is useful for 
- * obtaining optional values.
- * <p>
- * The generic <code>get()</code> and <code>opt()</code> methods return an 
- * object, which you can cast or query for type. There are also typed 
- * <code>get</code> and <code>opt</code> methods that do type checking and type 
- * coersion for you.
- * <p>
- * The <code>put</code> methods adds values to an object. For example, <pre>
- *     myString = new JSONObject().put("JSON", "Hello, World!").toString();</pre>
- * produces the string <code>{"JSON": "Hello, World"}</code>.
- * <p>
- * The texts produced by the <code>toString</code> methods strictly conform to
- * the JSON sysntax rules.
- * The constructors are more forgiving in the texts they will accept:
+ * A modifiable set of name/value mappings. Names are unique, non-null strings.
+ * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray
+ * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
+ * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link
+ * Double#isInfinite() infinities}, or of any type not listed here.
+ *
+ * <p>This class can coerce values to another type when requested.
  * <ul>
- * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just
- *     before the closing brace.</li>
- * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single
- *     quote)</small>.</li>
- * <li>Strings do not need to be quoted at all if they do not begin with a quote
- *     or single quote, and if they do not contain leading or trailing spaces,
- *     and if they do not contain any of these characters:
- *     <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers
- *     and if they are not the reserved words <code>true</code>,
- *     <code>false</code>, or <code>null</code>.</li>
- * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as
- *     by <code>:</code>.</li>
- * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as 
- *     well as by <code>,</code> <small>(comma)</small>.</li>
- * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or
- *     <code>0x-</code> <small>(hex)</small> prefix.</li>
- * <li>Comments written in the slashshlash, slashstar, and hash conventions
- *     will be ignored.</li>
+ *   <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)}. Although null cannot be
+ *       coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
+ *       string "null".
  * </ul>
- * @author JSON.org
- * @version 2
+ *
+ * <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} reference, and the sentinel value {@link
+ * 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;
+
     /**
-     * JSONObject.NULL is equivalent to the value that JavaScript calls null,
-     * whilst Java's null is equivalent to the value that JavaScript calls
-     * undefined.
+     * 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".
      */
-     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.
-         */
-        protected final Object clone() {
-            return this;
+    public static final Object NULL = new Object() {
+        @Override public boolean equals(Object o) {
+            return o == this || o == null; // API specifies this broken equals implementation
         }
-
-
-        /**
-         * 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.
-         */
-        public boolean equals(Object object) {
-            return object == null || object == this;
-        }
-
-
-        /**
-         * Get the "null" string value.
-         * @return The string "null".
-         */
-        public String toString() {
+        @Override public String toString() {
             return "null";
         }
-    }
+    };
 
+    private final Map<String, Object> nameValuePairs;
 
     /**
-     * The hash map where the JSONObject's properties are kept.
-     */
-    private HashMap myHashMap;
-
-
-    /**
-     * It is sometimes more convenient and less ambiguous to have a 
-     * <code>NULL</code> object than to use Java's <code>null</code> value.
-     * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
-     * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
-     */
-    public static final Object NULL = new Null();
-
-
-    /**
-     * Construct an empty JSONObject.
+     * Creates a {@code JSONObject} with no name/value mappings.
      */
     public JSONObject() {
-        this.myHashMap = new HashMap();
+        nameValuePairs = new HashMap<String, Object>();
     }
 
-
     /**
-     * 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 sa An array of strings.
-     * @exception JSONException If a value is a non-finite number.
+     * Creates a new {@code 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.
+     * @throws NullPointerException if any of the map's keys are null.
      */
-    public JSONObject(JSONObject jo, String[] sa) throws JSONException {
+    /* (accept a raw type for API compatibility) */
+    public JSONObject(Map copyFrom) {
         this();
-        for (int i = 0; i < sa.length; i += 1) {
-            putOpt(sa[i], jo.opt(sa[i]));
-        }
-    }
-
-
-    /**
-     * 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.
-     */
-    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();
-            }
-
+        Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
+        for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
             /*
-             * The key is followed by ':'. We will also tolerate '=' or '=>'.
+             * Deviate from the original by checking that keys are non-null and
+             * of the proper type. (We still defer validating the values).
              */
-
-            c = x.nextClean();
-            if (c == '=') {
-                if (x.next() != '>') {
-                    x.back();
-                }
-            } else if (c != ':') {
-                throw x.syntaxError("Expected a ':' after a key");
+            String key = (String) entry.getKey();
+            if (key == null) {
+                throw new NullPointerException();
             }
-            this.myHashMap.put(key, x.nextValue());
-
-            /*
-             * Pairs are separated by ','. We will also tolerate ';'.
-             */
-
-            switch (x.nextClean()) {
-            case ';':
-            case ',':
-                if (x.nextClean() == '}') {
-                    return;
-                }
-                x.back();
-                break;
-            case '}':
-                return;
-            default:
-                throw x.syntaxError("Expected a ',' or '}'");
-            }
+            nameValuePairs.put(key, entry.getValue());
         }
     }
 
-
     /**
-     * Construct a JSONObject from a Map.
-     * @param map A map object that can be used to initialize the contents of
-     *  the JSONObject.
+     * Creates a new {@code JSONObject} with name/value mappings from the next
+     * object in the tokener.
+     *
+     * @param readFrom a tokener whose nextValue() method will yield a
+     *     {@code JSONObject}.
+     * @throws JSONException if the parse fails or doesn't yield a
+     *     {@code JSONObject}.
      */
-    public JSONObject(Map map) {
-        this.myHashMap = new HashMap(map);
-    }
-
-
-    /**
-     * Construct a JSONObject from a string.
-     * This is the most commonly used JSONObject constructor.
-     * @param string    A string beginning
-     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
-     * @exception JSONException If there is a syntax error in the source string.
-     */
-    public JSONObject(String string) throws JSONException {
-        this(new JSONTokener(string));
-    }
-
-
-    /**
-     * 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.
-     * @param key   A key string.
-     * @param value An object to be accumulated under the key.
-     * @return this.
-     * @throws JSONException If the value is an invalid number 
-     *  or if the key is null.
-     */
-    public JSONObject accumulate(String key, Object value) 
-            throws JSONException {
-        testValidity(value);
-        Object o = opt(key);
-        if (o == null) {
-            put(key, value);
-        } else if (o instanceof JSONArray) {
-            ((JSONArray)o).put(value);
+    public JSONObject(JSONTokener readFrom) throws JSONException {
+        /*
+         * Getting the parser to populate this could get tricky. Instead, just
+         * parse to temporary JSONObject and then steal the data from that.
+         */
+        Object object = readFrom.nextValue();
+        if (object instanceof JSONObject) {
+            this.nameValuePairs = ((JSONObject) object).nameValuePairs;
         } else {
-            put(key, new JSONArray().put(o).put(value));
-        }
-        return this;
-    }
-
-
-    /**
-     * 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 {
-        Object o = opt(key);
-        if (o == null) {
-            throw new JSONException("JSONObject[" + quote(key) + 
-                    "] not found.");
-        }
-        return o;
-    }
-
-
-    /**
-     * 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 o = get(key);
-        if (o.equals(Boolean.FALSE) ||
-                (o instanceof String &&
-                ((String)o).equalsIgnoreCase("false"))) {
-            return false;
-        } else if (o.equals(Boolean.TRUE) ||
-                (o instanceof String &&
-                ((String)o).equalsIgnoreCase("true"))) {
-            return true;
-        }
-        throw new JSONException("JSONObject[" + quote(key) + 
-                "] is not a Boolean.");
-    }
-
-
-    /**
-     * 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 o = get(key);
-        try {
-            return o instanceof Number ? 
-                    ((Number)o).doubleValue() : Double.parseDouble((String)o);
-        } catch (Exception e) {
-            throw new JSONException("JSONObject[" + quote(key) + 
-                "] is not a number.");
+            throw JSON.typeMismatch(object, "JSONObject");
         }
     }
 
-
     /**
-     * Get the int value associated with a key. If the number value is too
-     * large for an int, it will be clipped.
+     * Creates a new {@code JSONObject} with name/value mappings from the JSON
+     * string.
      *
-     * @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.
+     * @param json a JSON-encoded string containing an object.
+     * @throws JSONException if the parse fails or doesn't yield a {@code
+     *     JSONObject}.
      */
-    public int getInt(String key) throws JSONException {
-        Object o = get(key);
-        return o instanceof Number ? 
-                ((Number)o).intValue() : (int)getDouble(key);
+    public JSONObject(String json) throws JSONException {
+        this(new JSONTokener(json));
     }
 
-
     /**
-     * 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.
+     * Creates a new {@code JSONObject} by copying mappings for the listed names
+     * from the given object. Names that aren't present in {@code copyFrom} will
+     * be skipped.
      */
-    public JSONArray getJSONArray(String key) throws JSONException {
-        Object o = get(key);
-        if (o instanceof JSONArray) {
-            return (JSONArray)o;
+    public JSONObject(JSONObject copyFrom, String[] names) throws JSONException {
+        this();
+        for (String name : names) {
+            Object value = copyFrom.opt(name);
+            if (value != null) {
+                nameValuePairs.put(name, value);
+            }
         }
-        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 o = get(key);
-        if (o instanceof JSONObject) {
-            return (JSONObject)o;
-        }
-        throw new JSONException("JSONObject[" + quote(key) + 
-                "] is not a JSONObject.");
-    }
-
-
-    /**
-     * Get the long value associated with a key. If the number value is too
-     * long for a long, it will be clipped.
-     *
-     * @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 o = get(key);
-        return o instanceof Number ? 
-                ((Number)o).longValue() : (long)getDouble(key);
-    }
-
-
-    /**
-     * Get the string associated with a key.
-     *
-     * @param key   A key string.
-     * @return      A string which is the value.
-     * @throws   JSONException if the key is not found.
-     */
-    public String getString(String key) throws JSONException {
-        return get(key).toString();
-    }
-
-
-    /**
-     * 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.myHashMap.containsKey(key);
-    }
-
-
-    /**
-     * 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(opt(key));
-    }
-
-
-    /**
-     * Get an enumeration of the keys of the JSONObject.
-     *
-     * @return An iterator of the keys.
-     */
-    public Iterator keys() {
-        return this.myHashMap.keySet().iterator();
-    }
-
-
-    /**
-     * Get the number of keys stored in the JSONObject.
-     *
-     * @return The number of keys in the JSONObject.
+     * Returns the number of name/value mappings in this object.
      */
     public int length() {
-        return this.myHashMap.size();
-    }
-
-
-    /**
-     * 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() {
-        JSONArray ja = new JSONArray();
-        Iterator  keys = keys();
-        while (keys.hasNext()) {
-            ja.put(keys.next());
-        }
-        return ja.length() == 0 ? null : ja;
+        return nameValuePairs.size();
     }
 
     /**
-     * Produce a string from a number.
-     * @param  n A Number
-     * @return A String.
-     * @throws JSONException If n is a non-finite number.
-     */
-    static public String numberToString(Number n) 
-            throws JSONException {
-        if (n == null) {
-            throw new JSONException("Null pointer");
-        }
-        testValidity(n);
-
-// Shave off trailing zeros and decimal point, if possible.
-
-        String s = n.toString();
-        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0) {
-            while (s.endsWith("0")) {
-                s = s.substring(0, s.length() - 1);
-            }
-            if (s.endsWith(".")) {
-                s = s.substring(0, s.length() - 1);
-            }
-        }
-        return s;
-    }
-
-
-    /**
-     * 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.myHashMap.get(key);
-    }
-
-
-    /**
-     * 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".
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
      *
-     * @param key   A key string.
-     * @return      The truth.
+     * @return this object.
      */
-    public boolean optBoolean(String key) {
-        return 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) {
-        try {
-            return getBoolean(key);
-        } 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 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) {
-        try {
-            Object o = opt(key);
-            return o instanceof Number ? ((Number)o).doubleValue() :
-                new Double((String)o).doubleValue();
-        } catch (Exception e) {
-            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 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) {
-        try {
-            return getInt(key);
-        } catch (Exception e) {
-            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 = 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 o = opt(key);
-        return o instanceof JSONObject ? (JSONObject)o : 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 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) {
-        try {
-            return getLong(key);
-        } catch (Exception e) {
-            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 coverted to a string.
-     *
-     * @param key   A key string.
-     * @return      A string which is the value.
-     */
-    public String optString(String key) {
-        return 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 o = opt(key);
-        return o != null ? o.toString() : defaultValue;
-    }
-
-
-    /**
-     * 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 key is null.
-     */
-    public JSONObject put(String key, boolean value) throws JSONException {
-        put(key, Boolean.valueOf(value));
+    public JSONObject put(String name, boolean value) throws JSONException {
+        nameValuePairs.put(checkName(name), value);
         return this;
     }
 
-
     /**
-     * Put a key/double pair in the JSONObject.
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
      *
-     * @param key   A key string.
-     * @param value A double which is the value.
-     * @return this.
-     * @throws JSONException If the key is null or if the number is invalid.
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this object.
      */
-    public JSONObject put(String key, double value) throws JSONException {
-        put(key, new Double(value));
+    public JSONObject put(String name, double value) throws JSONException {
+        nameValuePairs.put(checkName(name), JSON.checkDouble(value));
         return this;
     }
 
-
     /**
-     * Put a key/int pair in the JSONObject.
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
      *
-     * @param key   A key string.
-     * @param value An int which is the value.
-     * @return this.
-     * @throws JSONException If the key is null.
+     * @return this object.
      */
-    public JSONObject put(String key, int value) throws JSONException {
-        put(key, new Integer(value));
+    public JSONObject put(String name, int value) throws JSONException {
+        nameValuePairs.put(checkName(name), value);
         return this;
     }
 
-
     /**
-     * Put a key/long pair in the JSONObject.
+     * Maps {@code name} to {@code value}, clobbering any existing name/value
+     * mapping with the same name.
      *
-     * @param key   A key string.
-     * @param value A long which is the value.
-     * @return this.
-     * @throws JSONException If the key is null.
+     * @return this object.
      */
-    public JSONObject put(String key, long value) throws JSONException {
-        put(key, new Long(value));
+    public JSONObject put(String name, long value) throws JSONException {
+        nameValuePairs.put(checkName(name), value);
         return this;
     }
 
-
     /**
-     * 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 
-     *     or if the key is null.
+     * 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 key, Object value) 
-            throws JSONException {
-        if (key == null) {
-            throw new JSONException("Null key.");
+    public JSONObject put(String name, Object value) throws JSONException {
+        if (value == null) {
+            nameValuePairs.remove(name);
+            return this;
         }
-        if (value != null) {
-            testValidity(value);
-            this.myHashMap.put(key, value);
+        if (value instanceof Number) {
+            // deviate from the original by checking all Numbers, not just floats & doubles
+            JSON.checkDouble(((Number) value).doubleValue());
+        }
+        nameValuePairs.put(checkName(name), value);
+        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;
+        }
+        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) {
+            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 {
-            remove(key);
+            JSONArray array = new JSONArray();
+            array.put(current);
+            array.put(value);
+            nameValuePairs.put(name, array);
         }
         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) {
-            put(key, value);
+    String checkName(String name) throws JSONException {
+        if (name == null) {
+            throw new JSONException("Names must be non-null");
         }
-        return this;
-    }
-
-
-    /**
-     * Produce a string in double quotes with backslash sequences in all the
-     * right places. A backslash will be inserted within &lt;/, allowing JSON
-     * text to be delivered in HTML. In JSON text, a string cannot contain a
-     * control character or an unescaped quote or backslash.
-     * @param string A String
-     * @return  A String correctly formatted for insertion in a JSON text.
-     */
-    public static String quote(String string) {
-        if (string == null || string.length() == 0) {
-            return "\"\"";
-        }
-
-        char         b;
-        char         c = 0;
-        int          i;
-        int          len = string.length();
-        StringBuilder sb = new StringBuilder(len + 4);
-        String       t;
-
-        sb.append('"');
-        for (i = 0; i < len; i += 1) {
-            b = c;
-            c = string.charAt(i);
-            switch (c) {
-            case '\\':
-            case '"':
-                sb.append('\\');
-                sb.append(c);
-                break;
-            case '/':
-                if (b == '<') {
-                    sb.append('\\');
-                }
-                sb.append(c);
-                break;
-            case '\b':
-                sb.append("\\b");
-                break;
-            case '\t':
-                sb.append("\\t");
-                break;
-            case '\n':
-                sb.append("\\n");
-                break;
-            case '\f':
-                sb.append("\\f");
-                break;
-            case '\r':
-                sb.append("\\r");
-                break;
-            default:
-                if (c < ' ') {
-                    t = "000" + Integer.toHexString(c);
-                    sb.append("\\u" + t.substring(t.length() - 4));
-                } else {
-                    sb.append(c);
-                }
-            }
-        }
-        sb.append('"');
-        return sb.toString();
+        return name;
     }
 
     /**
-     * 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.
+     * 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 key) {
-        return this.myHashMap.remove(key);
+    public Object remove(String name) {
+        return nameValuePairs.remove(name);
     }
 
     /**
-     * Throw an exception if the object is an NaN or infinite number.
-     * @param o The object to test.
-     * @throws JSONException If o is a non-finite number. 
+     * Returns true if this object has no mapping for {@code name} or if it has
+     * a mapping whose value is {@link #NULL}.
      */
-    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.");
-                }
-            }
+    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) {
+            throw new JSONException("No value for " + name);
+        }
+        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);
+        if (result == null) {
+            throw JSON.typeMismatch(name, object, "boolean");
+        }
+        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);
+        if (result == null) {
+            throw JSON.typeMismatch(name, object, "double");
+        }
+        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);
+        if (result == null) {
+            throw JSON.typeMismatch(name, object, "int");
+        }
+        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);
+        if (result == null) {
+            throw JSON.typeMismatch(name, object, "long");
+        }
+        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);
+        if (result == null) {
+            throw JSON.typeMismatch(name, object, "String");
+        }
+        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) {
+            return (JSONArray) object;
+        } else {
+            throw JSON.typeMismatch(name, object, "JSONArray");
         }
     }
-    
-    
+
     /**
-     * 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.
+     * 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) {
+            return (JSONObject) object;
+        } else {
+            throw JSON.typeMismatch(name, object, "JSONObject");
+        }
+    }
+
+    /**
+     * 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 {
-        if (names == null || names.length() == 0) {
+        JSONArray result = new JSONArray();
+        if (names == null) {
             return null;
         }
-        JSONArray ja = new JSONArray();
-        for (int i = 0; i < names.length(); i += 1) {
-            ja.put(this.opt(names.getString(i)));
+        int length = names.length();
+        if (length == 0) {
+            return null;
         }
-        return ja;
+        for (int i = 0; i < length; i++) {
+            String name = JSON.toString(names.opt(i));
+            result.put(opt(name));
+        }
+        return result;
     }
 
     /**
-     * Make an 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.
-     * <p>
-     * Warning: This method assumes that the data structure is acyclical.
-     *
-     * @return a printable, displayable, portable, transmittable
-     *  representation of the object, beginning
-     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
+     * 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.
      */
-    public String toString() {
+    /* 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 {
-            Iterator     keys = keys();
-            StringBuilder sb = new StringBuilder("{");
-    
-            while (keys.hasNext()) {
-                if (sb.length() > 1) {
-                    sb.append(',');
-                }
-                Object o = keys.next();
-                sb.append(quote(o.toString()));
-                sb.append(':');
-                sb.append(valueToString(this.myHashMap.get(o)));
-            }
-            sb.append('}');
-            return sb.toString();
-        } catch (Exception e) {
+            JSONStringer stringer = new JSONStringer();
+            writeTo(stringer);
+            return stringer.toString();
+        } catch (JSONException e) {
             return null;
         }
     }
 
-
     /**
-     * Make a prettyprinted JSON text of this JSONObject.
-     * <p>
-     * 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 <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
-     * @throws JSONException If the object contains an invalid number.
+     * 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 indentFactor) throws JSONException {
-        return toString(indentFactor, 0);
+    public String toString(int indentSpaces) throws JSONException {
+        JSONStringer stringer = new JSONStringer(indentSpaces);
+        writeTo(stringer);
+        return stringer.toString();
     }
 
-
-    /**
-     * Make a prettyprinted JSON text of this JSONObject.
-     * <p>
-     * Warning: This method assumes that the data structure is acyclical.
-     * @param indentFactor The number of spaces to add to each level of
-     *  indentation.
-     * @param indent The indentation of the top level.
-     * @return a printable, displayable, transmittable
-     *  representation of the object, beginning
-     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
-     * @throws JSONException If the object contains an invalid number.
-     */
-    String toString(int indentFactor, int indent) throws JSONException {
-        int          i;
-        int          n = length();
-        if (n == 0) {
-            return "{}";
+    void writeTo(JSONStringer stringer) throws JSONException {
+        stringer.object();
+        for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
+            stringer.key(entry.getKey()).value(entry.getValue());
         }
-        Iterator     keys = keys();
-        StringBuilder sb = new StringBuilder("{");
-        int          newindent = indent + indentFactor;
-        Object       o;
-        if (n == 1) {
-            o = keys.next();
-            sb.append(quote(o.toString()));
-            sb.append(": ");
-            sb.append(valueToString(this.myHashMap.get(o), indentFactor, 
-                    indent));
-        } else {
-            while (keys.hasNext()) {
-                o = keys.next();
-                if (sb.length() > 1) {
-                    sb.append(",\n");
-                } else {
-                    sb.append('\n');
-                }
-                for (i = 0; i < newindent; i += 1) {
-                    sb.append(' ');
-                }
-                sb.append(quote(o.toString()));
-                sb.append(": ");
-                sb.append(valueToString(this.myHashMap.get(o), indentFactor,
-                        newindent));
-            }
-            if (sb.length() > 1) {
-                sb.append('\n');
-                for (i = 0; i < indent; i += 1) {
-                    sb.append(' ');
-                }
-            }
-        }
-        sb.append('}');
-        return sb.toString();
+        stringer.endObject();
     }
 
-
     /**
-     * Make a JSON text of an object value.
-     * <p>
-     * 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 <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
-     * @throws JSONException If the value is or contains an invalid number.
+     * Encodes the number as a JSON string.
+     *
+     * @param number a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
      */
-    static String valueToString(Object value) throws JSONException {
-        if (value == null || value.equals(null)) {
-            return "null";
+    public static String numberToString(Number number) throws JSONException {
+        if (number == null) {
+            throw new JSONException("Number must be non-null");
         }
-        if (value instanceof Number) {
-            return numberToString((Number) value);
+
+        double doubleValue = number.doubleValue();
+        JSON.checkDouble(doubleValue);
+
+        // the original returns "-0" instead of "-0.0" for negative zero
+        if (number.equals(NEGATIVE_ZERO)) {
+            return "-0";
         }
-        if (value instanceof Boolean || value instanceof JSONObject ||
-                value instanceof JSONArray) {
-            return value.toString();
+
+        long longValue = number.longValue();
+        if (doubleValue == (double) longValue) {
+            return Long.toString(longValue);
         }
-        return quote(value.toString());
+
+        return number.toString();
     }
 
-
     /**
-     * Make a prettyprinted JSON text of an object value.
-     * <p>
-     * Warning: This method assumes that the data structure is acyclical.
-     * @param value The value to be serialized.
-     * @param indentFactor The number of spaces to add to each level of
-     *  indentation.
-     * @param indent The indentation of the top level.
-     * @return a printable, displayable, transmittable
-     *  representation of the object, beginning
-     *  with <code>{</code>&nbsp;<small>(left brace)</small> and ending
-     *  with <code>}</code>&nbsp;<small>(right brace)</small>.
-     * @throws JSONException If the object contains an invalid number.
+     * 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.
      */
-     static String valueToString(Object value, int indentFactor, int indent) 
-             throws JSONException {
-        if (value == null || value.equals(null)) {
-            return "null";
+    public static String quote(String data) {
+        if (data == null) {
+            return "\"\"";
         }
-        if (value instanceof Number) {
-            return numberToString((Number) value);
+        try {
+            JSONStringer stringer = new JSONStringer();
+            stringer.open(JSONStringer.Scope.NULL, "");
+            stringer.value(data);
+            stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
+            return stringer.toString();
+        } catch (JSONException e) {
+            throw new AssertionError();
         }
-        if (value instanceof Boolean) {
-            return value.toString();
-        }
-        if (value instanceof JSONObject) {
-            return ((JSONObject)value).toString(indentFactor, indent);
-        }
-        if (value instanceof JSONArray) {
-            return ((JSONArray)value).toString(indentFactor, indent);
-        }
-        return quote(value.toString());
     }
 }
diff --git a/json/src/main/java/org/json/JSONStringer.java b/json/src/main/java/org/json/JSONStringer.java
index 1154823..38919053 100644
--- a/json/src/main/java/org/json/JSONStringer.java
+++ b/json/src/main/java/org/json/JSONStringer.java
@@ -1,327 +1,432 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.json;
 
-/*
-Copyright (c) 2005 JSON.org
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
+// Note: this class was written without inspecting the non-free org.json sourcecode.
 
 /**
- * JSONStringer provides a quick and convenient way of producing JSON text.
- * The texts produced strictly conform to JSON syntax rules. No whitespace is 
- * added, so the results are ready for transmission or storage. Each instance of 
- * JSONStringer can produce one JSON text.
- * <p>
- * A JSONStringer instance provides a <code>value</code> method for appending 
- * values to the 
- * text, and a <code>key</code> 
- * method for adding keys before values in objects. There are <code>array</code>
- * and <code>endArray</code> methods that make and bound array values, and 
- * <code>object</code> and <code>endObject</code> methods which make and bound 
- * object values. All of these methods return the JSONStringer instance, 
- * permitting cascade style. For example, <pre>
- * myString = new JSONStringer()
- *     .object()
- *         .key("JSON").value("Hello, World!")
- *     .endObject()
- *     .toString();</pre> which produces the string <pre>
- * {"JSON":"Hello, World!"}</pre>
- * <p>
- * The first method called must be <code>array</code> or <code>object</code>. 
- * There are no methods for adding commas or colons. JSONStringer adds them for 
- * you. Objects and arrays can be nested up to 20 levels deep.
- * <p>
- * This can sometimes be easier than using a JSONObject to build a string.
- * @author JSON.org
- * @version 2
+ * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
+ * application developers should use those methods directly and disregard this
+ * API. For example:<pre>
+ * JSONObject object = ...
+ * String json = object.toString();</pre>
+ *
+ * <p>Stringers only encode well-formed JSON strings. In particular:
+ * <ul>
+ *   <li>The stringer must have exactly one top-level array or object.
+ *   <li>Lexical scopes must be balanced: every call to {@link #array} must
+ *       have a matching call to {@link #endArray} and every call to {@link
+ *       #object} must have a matching call to {@link #endObject}.
+ *   <li>Arrays may not contain keys (property names).
+ *   <li>Objects must alternate keys (property names) and values.
+ *   <li>Values are inserted with either literal {@link #value(Object) value}
+ *       calls, or by nesting arrays or objects.
+ * </ul>
+ * Calls that would result in a malformed JSON string will fail with a
+ * {@link JSONException}.
+ *
+ * <p>This class provides no facility for pretty-printing (ie. indenting)
+ * output. To encode indented output, use {@link JSONObject#toString(int)} or
+ * {@link JSONArray#toString(int)}.
+ *
+ * <p>Some implementations of the API support at most 20 levels of nesting.
+ * Attempts to create more than 20 levels of nesting may fail with a {@link
+ * JSONException}.
+ *
+ * <p>Each stringer may be used to encode a single top level value. 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 JSONStringer {
-    private static final int maxdepth = 20;
-    
+
+    /** The output data, containing at most one top-level array or object. */
+    final StringBuilder out = new StringBuilder();
+
     /**
-     * The comma flag determines if a comma should be output before the next
-     * value.
+     * Lexical scoping elements within this stringer, necessary to insert the
+     * appropriate separator characters (ie. commas and colons) and to detect
+     * nesting errors.
      */
-    private boolean comma;
-    
-    /**
-     * The current mode. Values: 
-     * 'a' (array), 
-     * 'd' (done), 
-     * 'i' (initial), 
-     * 'k' (key), 
-     * 'o' (object).
-     */
-    private char mode;
-    
-    /**
-     * The string buffer that holds the JSON text that is built.
-     */
-    private StringBuilder sb;
-    
-    /**
-     * The object/array stack.
-     */
-    private char stack[];
-    
-    /**
-     * The stack top index. A value of 0 indicates that the stack is empty.
-     */
-    private int top;
-    
-    /**
-     * Make a fresh JSONStringer. It can be used to build one JSON text.
-     */
-    public JSONStringer() {
-        this.sb = new StringBuilder();
-        this.stack = new char[maxdepth];
-        this.top = 0;
-        this.mode = 'i';
-        this.comma = false;
-    }
-    
-    /**
-     * Append a value.
-     * @param s A string value.
-     * @return this
-     * @throws JSONException If the value is out of sequence.
-     */
-    private JSONStringer append(String s) 
-            throws JSONException {
-        if (s == null) {
-            throw new JSONException("Null pointer");
-        }
-        if (this.mode == 'o' || this.mode == 'a') {
-            if (this.comma && this.mode == 'a') {
-                this.sb.append(',');
-            }
-            this.sb.append(s);
-            if (this.mode == 'o') {
-                this.mode = 'k';
-            }
-            this.comma = true;
-            return this;
-        } 
-        throw new JSONException("Value out of sequence.");    
-    }
-    
-    /**
-     * Begin appending a new array. All values until the balancing 
-     * <code>endArray</code> will be appended to this array. The 
-     * <code>endArray</code> method must be called to mark the array's end.
-     * @return this
-     * @throws JSONException If the nesting is too deep, or if the object is 
-     * started in the wrong place (for example as a key or after the end of the 
-     * outermost array or object).
-     */
-    public JSONStringer array() throws JSONException {
-        if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
-            push('a');
-            this.append("[");
-            this.comma = false;
-            return this;
-        } 
-        throw new JSONException("Misplaced array.");
-    }
-    
-    /**
-     * End something. 
-     * @param m Mode
-     * @param c Closing character
-     * @return this
-     * @throws JSONException If unbalanced.
-     */
-    private JSONStringer end(char m, char c) throws JSONException {
-        if (this.mode != m) {
-            throw new JSONException(m == 'o' ? "Misplaced endObject." :
-                "Misplaced endArray.");            
-        }
-        pop(m);
-        this.sb.append(c);
-        this.comma = true;
-        return this;
-    }
-    
-    /**
-     * End an array. This method most be called to balance calls to 
-     * <code>array</code>.
-     * @return this
-     * @throws JSONException If incorrectly nested.
-     */
-    public JSONStringer endArray() throws JSONException {
-        return end('a', ']');
-    }
-    
-    /**
-     * End an object. This method most be called to balance calls to 
-     * <code>object</code>.
-     * @return this
-     * @throws JSONException If incorrectly nested.
-     */
-    public JSONStringer endObject() throws JSONException {
-        return end('k', '}');
-    }
-    
-    /**
-     * Append a key. The key will be associated with the next value. In an
-     * object, every value must be preceded by a key.
-     * @param s A key string.
-     * @return this
-     * @throws JSONException If the key is out of place. For example, keys
-     *     do not belong in arrays or if the key is null.
-     */
-    public JSONStringer key(String s) 
-            throws JSONException {
-        if (s == null) {
-            throw new JSONException("Null key.");
-        }
-        if (this.mode == 'k') {
-            if (this.comma) {
-                this.sb.append(',');
-            }
-            this.sb.append(JSONObject.quote(s));
-            this.sb.append(':');
-            this.comma = false;
-            this.mode = 'o';
-            return this;
-        } 
-        throw new JSONException("Misplaced key.");    
+    enum Scope {
+
+        /**
+         * An array with no elements requires no separators or newlines before
+         * it is closed.
+         */
+        EMPTY_ARRAY,
+
+        /**
+         * A array with at least one value requires a comma and newline before
+         * the next element.
+         */
+        NONEMPTY_ARRAY,
+
+        /**
+         * An object with no keys or values requires no separators or newlines
+         * before it is closed.
+         */
+        EMPTY_OBJECT,
+
+        /**
+         * An object whose most recent element is a key. The next element must
+         * be a value.
+         */
+        DANGLING_KEY,
+
+        /**
+         * An object with at least one name/value pair requires a comma and
+         * newline before the next element.
+         */
+        NONEMPTY_OBJECT,
+
+        /**
+         * A special bracketless array needed by JSONStringer.join() and
+         * JSONObject.quote() only. Not used for JSON encoding.
+         */
+        NULL,
     }
 
-    
     /**
-     * Begin appending a new object. All keys and values until the balancing 
-     * <code>endObject</code> will be appended to this object. The 
-     * <code>endObject</code> method must be called to mark the object's end.
-     * @return this
-     * @throws JSONException If the nesting is too deep, or if the object is 
-     * started in the wrong place (for example as a key or after the end of the 
-     * outermost array or object).
+     * Unlike the original implementation, this stack isn't limited to 20
+     * levels of nesting.
+     */
+    private final List<Scope> stack = new ArrayList<Scope>();
+
+    /**
+     * A string containing a full set of spaces for a single level of
+     * indentation, or null for no pretty printing.
+     */
+    private final String indent;
+
+    public JSONStringer() {
+        indent = null;
+    }
+
+    JSONStringer(int indentSpaces) {
+        char[] indentChars = new char[indentSpaces];
+        Arrays.fill(indentChars, ' ');
+        indent = new String(indentChars);
+    }
+
+    /**
+     * Begins encoding a new array. Each call to this method must be paired with
+     * a call to {@link #endArray}.
+     *
+     * @return this stringer.
+     */
+    public JSONStringer array() throws JSONException {
+        return open(Scope.EMPTY_ARRAY, "[");
+    }
+
+    /**
+     * Ends encoding the current array.
+     *
+     * @return this stringer.
+     */
+    public JSONStringer endArray() throws JSONException {
+        return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
+    }
+
+    /**
+     * Begins encoding a new object. Each call to this method must be paired
+     * with a call to {@link #endObject}.
+     *
+     * @return this stringer.
      */
     public JSONStringer object() throws JSONException {
-        if (this.mode == 'i') {
-            this.mode = 'o';
+        return open(Scope.EMPTY_OBJECT, "{");
+    }
+
+    /**
+     * Ends encoding the current object.
+     *
+     * @return this stringer.
+     */
+    public JSONStringer endObject() throws JSONException {
+        return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
+    }
+
+    /**
+     * Enters a new scope by appending any necessary whitespace and the given
+     * bracket.
+     */
+    JSONStringer open(Scope empty, String openBracket) throws JSONException {
+        if (stack.isEmpty() && out.length() > 0) {
+            throw new JSONException("Nesting problem: multiple top-level roots");
         }
-        if (this.mode == 'o' || this.mode == 'a') {
-            this.append("{");
-            push('k');
-            this.comma = false;
+        beforeValue();
+        stack.add(empty);
+        out.append(openBracket);
+        return this;
+    }
+
+    /**
+     * Closes the current scope by appending any necessary whitespace and the
+     * given bracket.
+     */
+    JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
+        Scope context = peek();
+        if (context != nonempty && context != empty) {
+            throw new JSONException("Nesting problem");
+        }
+
+        stack.remove(stack.size() - 1);
+        if (context == nonempty) {
+            newline();
+        }
+        out.append(closeBracket);
+        return this;
+    }
+
+    /**
+     * Returns the value on the top of the stack.
+     */
+    private Scope peek() throws JSONException {
+        if (stack.isEmpty()) {
+            throw new JSONException("Nesting problem");
+        }
+        return stack.get(stack.size() - 1);
+    }
+
+    /**
+     * Replace the value on the top of the stack with the given value.
+     */
+    private void replaceTop(Scope topOfStack) {
+        stack.set(stack.size() - 1, topOfStack);
+    }
+
+    /**
+     * Encodes {@code value}.
+     *
+     * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
+     *     or {@link Double#isInfinite() infinities}.
+     * @return this stringer.
+     */
+    public JSONStringer value(Object value) throws JSONException {
+        if (stack.isEmpty()) {
+            throw new JSONException("Nesting problem");
+        }
+
+        if (value instanceof JSONArray) {
+            ((JSONArray) value).writeTo(this);
             return this;
-        } 
-        throw new JSONException("Misplaced object.");
-        
-    }
-    
-    
-    /**
-     * Pop an array or object scope.
-     * @param c The scope to close.
-     * @throws JSONException If nesting is wrong.
-     */
-    private void pop(char c) throws JSONException {
-        if (this.top <= 0 || this.stack[this.top - 1] != c) {
-            throw new JSONException("Nesting error.");
+
+        } else if (value instanceof JSONObject) {
+            ((JSONObject) value).writeTo(this);
+            return this;
         }
-        this.top -= 1;
-        this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1];
-    }
-    
-    /**
-     * Push an array or object scope.
-     * @param c The scope to open.
-     * @throws JSONException If nesting is too deep.
-     */
-    private void push(char c) throws JSONException {
-        if (this.top >= maxdepth) {
-            throw new JSONException("Nesting too deep.");
+
+        beforeValue();
+
+        if (value == null
+                || value instanceof Boolean
+                || value == JSONObject.NULL) {
+            out.append(value);
+
+        } else if (value instanceof Number) {
+            out.append(JSONObject.numberToString((Number) value));
+
+        } else {
+            string(value.toString());
         }
-        this.stack[this.top] = c;
-        this.mode = c;
-        this.top += 1;
+
+        return this;
     }
-    
-    
+
     /**
-     * Append either the value <code>true</code> or the value 
-     * <code>false</code>. 
-     * @param b A boolean.
-     * @return this
-     * @throws JSONException
+     * Encodes {@code value} to this stringer.
+     *
+     * @return this stringer.
      */
-    public JSONStringer value(boolean b) throws JSONException {
-        return this.append(b ? "true" : "false");
-    }
-    
-    /**
-     * Append a double value.
-     * @param d A double.
-     * @return this
-     * @throws JSONException If the number is not finite.
-     */
-    public JSONStringer value(double d) throws JSONException {
-        return this.value(new Double(d));
-    }
-    
-    /**
-     * Append a long value.
-     * @param l A long.
-     * @return this
-     * @throws JSONException
-     */
-    public JSONStringer value(long l) throws JSONException {
-        return this.append(Long.toString(l));
-    }
-    
-    
-    /**
-     * Append an object value.
-     * @param o The object to append. It can be null, or a Boolean, Number,
-     *   String, JSONObject, or JSONArray.
-     * @return this
-     * @throws JSONException If the value is out of sequence.
-     */
-    public JSONStringer value(Object o) throws JSONException {
-        if (JSONObject.NULL.equals(o)) {
-            return this.append("null");
+    public JSONStringer value(boolean value) throws JSONException {
+        if (stack.isEmpty()) {
+            throw new JSONException("Nesting problem");
         }
-        if (o instanceof Number) {
-            JSONObject.testValidity(o);
-            return this.append(JSONObject.numberToString((Number)o));
-        }
-        if (o instanceof Boolean || 
-                o instanceof JSONArray || o instanceof JSONObject) {
-            return this.append(o.toString());
-        }
-        return this.append(JSONObject.quote(o.toString()));
+        beforeValue();
+        out.append(value);
+        return this;
     }
-    
+
     /**
-     * Return the JSON text. This method is used to obtain the product of the
-     * JSONStringer instance. It will return <code>null</code> if there was a 
-     * problem in the construction of the JSON text (such as the calls to 
-     * <code>array</code> were not properly balanced with calls to 
-     * <code>endArray</code>).
-     * @return The JSON text.
+     * Encodes {@code value} to this stringer.
+     *
+     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+     *     {@link Double#isInfinite() infinities}.
+     * @return this stringer.
      */
-    public String toString() {
-        return this.mode == 'd' ? this.sb.toString() : null;
+    public JSONStringer value(double value) throws JSONException {
+        if (stack.isEmpty()) {
+            throw new JSONException("Nesting problem");
+        }
+        beforeValue();
+        out.append(JSONObject.numberToString(value));
+        return this;
+    }
+
+    /**
+     * Encodes {@code value} to this stringer.
+     *
+     * @return this stringer.
+     */
+    public JSONStringer value(long value) throws JSONException {
+        if (stack.isEmpty()) {
+            throw new JSONException("Nesting problem");
+        }
+        beforeValue();
+        out.append(value);
+        return this;
+    }
+
+    private void string(String value) {
+        out.append("\"");
+        for (int i = 0, length = value.length(); i < length; i++) {
+            char c = value.charAt(i);
+
+            /*
+             * From RFC 4627, "All Unicode characters may be placed within the
+             * quotation marks except for the characters that must be escaped:
+             * quotation mark, reverse solidus, and the control characters
+             * (U+0000 through U+001F)."
+             */
+            switch (c) {
+                case '"':
+                case '\\':
+                case '/':
+                    out.append('\\').append(c);
+                    break;
+
+                case '\t':
+                    out.append("\\t");
+                    break;
+
+                case '\b':
+                    out.append("\\b");
+                    break;
+
+                case '\n':
+                    out.append("\\n");
+                    break;
+
+                case '\r':
+                    out.append("\\r");
+                    break;
+
+                case '\f':
+                    out.append("\\f");
+                    break;
+
+                default:
+                    if (c <= 0x1F) {
+                        out.append(String.format("\\u%04x", (int) c));
+                    } else {
+                        out.append(c);
+                    }
+                    break;
+            }
+
+        }
+        out.append("\"");
+    }
+
+    private void newline() {
+        if (indent == null) {
+            return;
+        }
+
+        out.append("\n");
+        for (int i = 0; i < stack.size(); i++) {
+            out.append(indent);
+        }
+    }
+
+    /**
+     * Encodes the key (property name) to this stringer.
+     *
+     * @param name the name of the forthcoming value. May not be null.
+     * @return this stringer.
+     */
+    public JSONStringer key(String name) throws JSONException {
+        if (name == null) {
+            throw new JSONException("Names must be non-null");
+        }
+        beforeKey();
+        string(name);
+        return this;
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a name. Also
+     * adjusts the stack to expect the key's value.
+     */
+    private void beforeKey() throws JSONException {
+        Scope context = peek();
+        if (context == Scope.NONEMPTY_OBJECT) { // first in object
+            out.append(',');
+        } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
+            throw new JSONException("Nesting problem");
+        }
+        newline();
+        replaceTop(Scope.DANGLING_KEY);
+    }
+
+    /**
+     * Inserts any necessary separators and whitespace before a literal value,
+     * inline array, or inline object. Also adjusts the stack to expect either a
+     * closing bracket or another element.
+     */
+    private void beforeValue() throws JSONException {
+        if (stack.isEmpty()) {
+            return;
+        }
+
+        Scope context = peek();
+        if (context == Scope.EMPTY_ARRAY) { // first in array
+            replaceTop(Scope.NONEMPTY_ARRAY);
+            newline();
+        } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
+            out.append(',');
+            newline();
+        } else if (context == Scope.DANGLING_KEY) { // value for key
+            out.append(indent == null ? ":" : ": ");
+            replaceTop(Scope.NONEMPTY_OBJECT);
+        } else if (context != Scope.NULL) {
+            throw new JSONException("Nesting problem");
+        }
+    }
+
+    /**
+     * Returns the encoded JSON string.
+     *
+     * <p>If invoked with unterminated arrays or unclosed objects, this method's
+     * return value is undefined.
+     *
+     * <p><strong>Warning:</strong> although it contradicts the general contract
+     * of {@link Object#toString}, this method returns null if the stringer
+     * contains no data.
+     */
+    @Override public String toString() {
+        return out.length() == 0 ? null : out.toString();
     }
 }
diff --git a/json/src/main/java/org/json/JSONTokener.java b/json/src/main/java/org/json/JSONTokener.java
index 7ac1cd7..3a82ab9 100644
--- a/json/src/main/java/org/json/JSONTokener.java
+++ b/json/src/main/java/org/json/JSONTokener.java
@@ -1,460 +1,607 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package org.json;
 
-/*
-Copyright (c) 2002 JSON.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-The Software shall be used for Good, not Evil.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-*/
+// Note: this class was written without inspecting the non-free org.json sourcecode.
 
 /**
- * A JSONTokener takes a source string and extracts characters and tokens from
- * it. It is used by the JSONObject and JSONArray constructors to parse
- * JSON source strings.
- * @author JSON.org
- * @version 2
+ * Parses a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded string into the corresponding object. Most clients of
+ * this class will use only need the {@link #JSONTokener(String) constructor}
+ * and {@link #nextValue} method. Example usage: <pre>
+ * String json = "{"
+ *         + "  \"query\": \"Pizza\", "
+ *         + "  \"locations\": [ 94043, 90210 ] "
+ *         + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");</pre>
+ *
+ * <p>For best interoperability and performance use JSON that complies with
+ * RFC 4627, such as that generated by {@link JSONStringer}. For legacy reasons
+ * this parser is lenient, so a successful parse does not indicate that the
+ * input string was valid JSON. All of the following syntax errors will be
+ * ignored:
+ * <ul>
+ *   <li>End of line comments starting with {@code //} or {@code #} and ending
+ *       with a newline character.
+ *   <li>C-style comments starting with {@code /*} and ending with
+ *       {@code *}{@code /}. Such comments may not be nested.
+ *   <li>Strings that are unquoted or {@code 'single quoted'}.
+ *   <li>Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
+ *   <li>Octal integers prefixed with {@code 0}.
+ *   <li>Array elements separated by {@code ;}.
+ *   <li>Unnecessary array separators. These are interpretted as if null was the
+ *       omitted value.
+ *   <li>Key-value pairs separated by {@code =} or {@code =>}.
+ *   <li>Key-value pairs separated by {@code ;}.
+ * </ul>
+ *
+ * <p>Each tokener may be used to parse a single JSON string. 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 JSONTokener {
 
+    /** The input JSON. */
+    private final String in;
+
     /**
-     * The index of the next character.
+     * The index of the next character to be returned by {@link #next}. When
+     * the input is exhausted, this equals the input's length.
      */
-    private int myIndex;
-
+    private int pos;
 
     /**
-     * The source string being tokenized.
+     * @param in JSON encoded string. Null is not permitted and will yield a
+     *     tokener that throws {@code NullPointerExceptions} when methods are
+     *     called.
      */
-    private String mySource;
-
+    public JSONTokener(String in) {
+        this.in = in;
+    }
 
     /**
-     * Construct a JSONTokener from a string.
+     * Returns the next value from the input.
      *
-     * @param s     A source string.
+     * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+     *     Integer, Long, Double or {@link JSONObject#NULL}.
+     * @throws JSONException if the input is malformed.
      */
-    public JSONTokener(String s) {
-        this.myIndex = 0;
-        this.mySource = s;
-    }
+    public Object nextValue() throws JSONException {
+        int c = nextCleanInternal();
+        switch (c) {
+            case -1:
+                throw syntaxError("End of input");
 
+            case '{':
+                return readObject();
 
-    /**
-     * Back up one character. This provides a sort of lookahead capability,
-     * so that you can test for a digit or letter before attempting to parse
-     * the next number or identifier.
-     */
-    public void back() {
-        if (this.myIndex > 0) {
-            this.myIndex -= 1;
+            case '[':
+                return readArray();
+
+            case '\'':
+            case '"':
+                return nextString((char) c);
+
+            default:
+                pos--;
+                return readLiteral();
         }
     }
 
+    private int nextCleanInternal() throws JSONException {
+        while (pos < in.length()) {
+            int c = in.charAt(pos++);
+            switch (c) {
+                case '\t':
+                case ' ':
+                case '\n':
+                case '\r':
+                    continue;
 
+                case '/':
+                    if (pos == in.length()) {
+                        return c;
+                    }
 
-    /**
-     * Get the hex value of a character (base16).
-     * @param c A character between '0' and '9' or between 'A' and 'F' or
-     * between 'a' and 'f'.
-     * @return  An int between 0 and 15, or -1 if c was not a hex digit.
-     */
-    public static int dehexchar(char c) {
-        if (c >= '0' && c <= '9') {
-            return c - '0';
+                    char peek = in.charAt(pos);
+                    switch (peek) {
+                        case '*':
+                            // skip a /* c-style comment */
+                            pos++;
+                            int commentEnd = in.indexOf("*/", pos);
+                            if (commentEnd == -1) {
+                                throw syntaxError("Unterminated comment");
+                            }
+                            pos = commentEnd + 2;
+                            continue;
+
+                        case '/':
+                            // skip a // end-of-line comment
+                            pos++;
+                            skipToEndOfLine();
+                            continue;
+
+                        default:
+                            return c;
+                    }
+
+                case '#':
+                    /*
+                     * Skip a # hash end-of-line comment. The JSON RFC doesn't
+                     * specify this behaviour, but it's required to parse
+                     * existing documents. See http://b/2571423.
+                     */
+                    skipToEndOfLine();
+                    continue;
+
+                default:
+                    return c;
+            }
         }
-        if (c >= 'A' && c <= 'F') {
-            return c - ('A' - 10);
-        }
-        if (c >= 'a' && c <= 'f') {
-            return c - ('a' - 10);
-        }
+
         return -1;
     }
 
-
     /**
-     * Determine if the source string still contains characters that next()
-     * can consume.
-     * @return true if not yet at the end of the source.
+     * Advances the position until after the next newline character. If the line
+     * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+     * caller.
      */
-    public boolean more() {
-        return this.myIndex < this.mySource.length();
-    }
-
-
-    /**
-     * Get the next character in the source string.
-     *
-     * @return The next character, or 0 if past the end of the source string.
-     */
-    public char next() {
-        if (more()) {
-            char c = this.mySource.charAt(this.myIndex);
-            this.myIndex += 1;
-            return c;
-        }
-        return 0;
-    }
-
-
-    /**
-     * Consume the next character, and check that it matches a specified
-     * character.
-     * @param c The character to match.
-     * @return The character.
-     * @throws JSONException if the character does not match.
-     */
-    public char next(char c) throws JSONException {
-        char n = next();
-        if (n != c) {
-            throw syntaxError("Expected '" + c + "' and instead saw '" +
-                    n + "'.");
-        }
-        return n;
-    }
-
-
-    /**
-     * Get the next n characters.
-     *
-     * @param n     The number of characters to take.
-     * @return      A string of n characters.
-     * @throws JSONException
-     *   Substring bounds error if there are not
-     *   n characters remaining in the source string.
-     */
-     public String next(int n) throws JSONException {
-         int i = this.myIndex;
-         int j = i + n;
-         if (j >= this.mySource.length()) {
-            throw syntaxError("Substring bounds error");
-         }
-         this.myIndex += n;
-         return this.mySource.substring(i, j);
-     }
-
-
-    /**
-     * Get the next char in the string, skipping whitespace
-     * and comments (slashslash, slashstar, and hash).
-     * @throws JSONException
-     * @return  A character, or 0 if there are no more characters.
-     */
-    public char nextClean() throws JSONException {
-        for (;;) {
-            char c = next();
-            if (c == '/') {
-                switch (next()) {
-                case '/':
-                    do {
-                        c = next();
-                    } while (c != '\n' && c != '\r' && c != 0);
-                    break;
-                case '*':
-                    for (;;) {
-                        c = next();
-                        if (c == 0) {
-                            throw syntaxError("Unclosed comment.");
-                        }
-                        if (c == '*') {
-                            if (next() == '/') {
-                                break;
-                            }
-                            back();
-                        }
-                    }
-                    break;
-                default:
-                    back();
-                    return '/';
-                }
-            } else if (c == '#') {
-                do {
-                    c = next();
-                } while (c != '\n' && c != '\r' && c != 0);
-            } else if (c == 0 || c > ' ') {
-                return c;
+    private void skipToEndOfLine() {
+        for (; pos < in.length(); pos++) {
+            char c = in.charAt(pos);
+            if (c == '\r' || c == '\n') {
+                pos++;
+                break;
             }
         }
     }
 
-
     /**
-     * Return the characters up to the next close quote character.
-     * Backslash processing is done. The formal JSON format does not
-     * allow strings in single quotes, but an implementation is allowed to
-     * accept them.
-     * @param quote The quoting character, either
-     *      <code>"</code>&nbsp;<small>(double quote)</small> or
-     *      <code>'</code>&nbsp;<small>(single quote)</small>.
-     * @return      A String.
-     * @throws JSONException Unterminated string.
+     * Returns the string up to but not including {@code quote}, unescaping any
+     * character escape sequences encountered along the way. The opening quote
+     * should have already been read. This consumes the closing quote, but does
+     * not include it in the returned string.
+     *
+     * @param quote either ' or ".
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
      */
     public String nextString(char quote) throws JSONException {
-        char c;
-        StringBuilder sb = new StringBuilder();
-        for (;;) {
-            c = next();
-            switch (c) {
-            case 0:
-            case '\n':
-            case '\r':
-                throw syntaxError("Unterminated string");
-            case '\\':
-                c = next();
-                switch (c) {
-                case 'b':
-                    sb.append('\b');
-                    break;
-                case 't':
-                    sb.append('\t');
-                    break;
-                case 'n':
-                    sb.append('\n');
-                    break;
-                case 'f':
-                    sb.append('\f');
-                    break;
-                case 'r':
-                    sb.append('\r');
-                    break;
-                case 'u':
-                    sb.append((char)Integer.parseInt(next(4), 16));
-                    break;
-                case 'x' :
-                    sb.append((char) Integer.parseInt(next(2), 16));
-                    break;
-                default:
-                    sb.append(c);
+        /*
+         * For strings that are free of escape sequences, we can just extract
+         * the result as a substring of the input. But if we encounter an escape
+         * sequence, we need to use a StringBuilder to compose the result.
+         */
+        StringBuilder builder = null;
+
+        /* the index of the first character not yet appended to the builder. */
+        int start = pos;
+
+        while (pos < in.length()) {
+            int c = in.charAt(pos++);
+            if (c == quote) {
+                if (builder == null) {
+                    // a new string avoids leaking memory
+                    return new String(in.substring(start, pos - 1));
+                } else {
+                    builder.append(in, start, pos - 1);
+                    return builder.toString();
                 }
-                break;
-            default:
-                if (c == quote) {
-                    return sb.toString();
+            }
+
+            if (c == '\\') {
+                if (pos == in.length()) {
+                    throw syntaxError("Unterminated escape sequence");
                 }
-                sb.append(c);
+                if (builder == null) {
+                    builder = new StringBuilder();
+                }
+                builder.append(in, start, pos - 1);
+                builder.append(readEscapeCharacter());
+                start = pos;
             }
         }
+
+        throw syntaxError("Unterminated string");
     }
 
-
     /**
-     * Get the text up but not including the specified character or the
-     * end of line, whichever comes first.
-     * @param  d A delimiter character.
-     * @return   A string.
-     */
-    public String nextTo(char d) {
-        StringBuilder sb = new StringBuilder();
-        for (;;) {
-            char c = next();
-            if (c == d || c == 0 || c == '\n' || c == '\r') {
-                if (c != 0) {
-                    back();
-                }
-                return sb.toString().trim();
-            }
-            sb.append(c);
-        }
-    }
-
-
-    /**
-     * Get the text up but not including one of the specified delimeter
-     * characters or the end of line, whichever comes first.
-     * @param delimiters A set of delimiter characters.
-     * @return A string, trimmed.
-     */
-    public String nextTo(String delimiters) {
-        char c;
-        StringBuilder sb = new StringBuilder();
-        for (;;) {
-            c = next();
-            if (delimiters.indexOf(c) >= 0 || c == 0 ||
-                    c == '\n' || c == '\r') {
-                if (c != 0) {
-                    back();
-                }
-                return sb.toString().trim();
-            }
-            sb.append(c);
-        }
-    }
-
-
-    /**
-     * Get the next value. The value can be a Boolean, Double, Integer,
-     * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
-     * @throws JSONException If syntax error.
+     * Unescapes the character identified by the character or characters that
+     * immediately follow a backslash. The backslash '\' should have already
+     * been read. This supports both unicode escapes "u000A" and two-character
+     * escapes "\n".
      *
-     * @return An object.
+     * @throws NumberFormatException if any unicode escape sequences are
+     *     malformed.
      */
-    public Object nextValue() throws JSONException {
-        char c = nextClean();
-        String s;
+    private char readEscapeCharacter() throws JSONException {
+        char escaped = in.charAt(pos++);
+        switch (escaped) {
+            case 'u':
+                if (pos + 4 > in.length()) {
+                    throw syntaxError("Unterminated escape sequence");
+                }
+                String hex = in.substring(pos, pos + 4);
+                pos += 4;
+                return (char) Integer.parseInt(hex, 16);
 
-        switch (c) {
-            case '"':
+            case 't':
+                return '\t';
+
+            case 'b':
+                return '\b';
+
+            case 'n':
+                return '\n';
+
+            case 'r':
+                return '\r';
+
+            case 'f':
+                return '\f';
+
             case '\'':
-                return nextString(c);
-            case '{':
-                back();
-                return new JSONObject(this);
-            case '[':
-                back();
-                return new JSONArray(this);
+            case '"':
+            case '\\':
+            default:
+                return escaped;
         }
+    }
 
-        /*
-         * Handle unquoted text. This could be the values true, false, or
-         * null, or it can be a number. An implementation (such as this one)
-         * is allowed to also accept non-standard forms.
-         *
-         * Accumulate characters until we reach the end of the text or a
-         * formatting character.
-         */
+    /**
+     * Reads a null, boolean, numeric or unquoted string literal value. Numeric
+     * values will be returned as an Integer, Long, or Double, in that order of
+     * preference.
+     */
+    private Object readLiteral() throws JSONException {
+        String literal = nextToInternal("{}[]/\\:,=;# \t\f");
 
-        StringBuilder sb = new StringBuilder();
-        char b = c;
-        while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
-            sb.append(c);
-            c = next();
-        }
-        back();
-
-        /*
-         * If it is true, false, or null, return the proper value.
-         */
-
-        s = sb.toString().trim();
-        if (s.equals("")) {
-            throw syntaxError("Missing value.");
-        }
-        if (s.equalsIgnoreCase("true")) {
+        if (literal.length() == 0) {
+            throw syntaxError("Expected literal value");
+        } else if ("null".equalsIgnoreCase(literal)) {
+            return JSONObject.NULL;
+        } else if ("true".equalsIgnoreCase(literal)) {
             return Boolean.TRUE;
-        }
-        if (s.equalsIgnoreCase("false")) {
+        } else if ("false".equalsIgnoreCase(literal)) {
             return Boolean.FALSE;
         }
-        if (s.equalsIgnoreCase("null")) {
-            return JSONObject.NULL;
-        }
 
-        /*
-         * If it might be a number, try converting it. We support the 0- and 0x-
-         * conventions. If a number cannot be produced, then the value will just
-         * be a string. Note that the 0-, 0x-, plus, and implied string
-         * conventions are non-standard. A JSON parser is free to accept
-         * non-JSON forms as long as it accepts all correct JSON forms.
-         */
-
-        if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') {
-            if (b == '0') {
-                if (s.length() > 2 &&
-                        (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
-                    try {
-                        return new Integer(Integer.parseInt(s.substring(2), 
-                                16));
-                    } catch (Exception e) {
-                        /* Ignore the error */
-                    }
-                } else {
-                    try {
-                        return new Integer(Integer.parseInt(s, 8));
-                    } catch (Exception e) {
-                        /* Ignore the error */
-                    }
-                }
+        /* try to parse as an integral type... */
+        if (literal.indexOf('.') == -1) {
+            int base = 10;
+            String number = literal;
+            if (number.startsWith("0x") || number.startsWith("0X")) {
+                number = number.substring(2);
+                base = 16;
+            } else if (number.startsWith("0") && number.length() > 1) {
+                number = number.substring(1);
+                base = 8;
             }
             try {
-                return new Integer(s);
-            } catch (Exception e) {
-                try {
-                    return new Long(s);
-                } catch (Exception f) {
-                    try {
-                        return new Double(s);
-                    }  catch (Exception g) {
-                        return s;
-                    }
+                long longValue = Long.parseLong(number, base);
+                if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) {
+                    return (int) longValue;
+                } else {
+                    return longValue;
+                }
+            } catch (NumberFormatException e) {
+                /*
+                 * This only happens for integral numbers greater than
+                 * Long.MAX_VALUE, numbers in exponential form (5e-10) and
+                 * unquoted strings. Fall through to try floating point.
+                 */
+            }
+        }
+
+        /* ...next try to parse as a floating point... */
+        try {
+            return Double.valueOf(literal);
+        } catch (NumberFormatException e) {
+        }
+
+        /* ... finally give up. We have an unquoted string */
+        return new String(literal); // a new string avoids leaking memory
+    }
+
+    /**
+     * Returns the string up to but not including any of the given characters or
+     * a newline character. This does not consume the excluded character.
+     */
+    private String nextToInternal(String excluded) {
+        int start = pos;
+        for (; pos < in.length(); pos++) {
+            char c = in.charAt(pos);
+            if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) {
+                return in.substring(start, pos);
+            }
+        }
+        return in.substring(start);
+    }
+
+    /**
+     * Reads a sequence of key/value pairs and the trailing closing brace '}' of
+     * an object. The opening brace '{' should have already been read.
+     */
+    private JSONObject readObject() throws JSONException {
+        JSONObject result = new JSONObject();
+
+        /* Peek to see if this is the empty object. */
+        int first = nextCleanInternal();
+        if (first == '}') {
+            return result;
+        } else if (first != -1) {
+            pos--;
+        }
+
+        while (true) {
+            Object name = nextValue();
+            if (!(name instanceof String)) {
+                if (name == null) {
+                    throw syntaxError("Names cannot be null");
+                } else {
+                    throw syntaxError("Names must be strings, but " + name
+                            + " is of type " + name.getClass().getName());
                 }
             }
-        }
-        return s;
-    }
 
-
-    /**
-     * Skip characters until the next character is the requested character.
-     * If the requested character is not found, no characters are skipped.
-     * @param to A character to skip to.
-     * @return The requested character, or zero if the requested character
-     * is not found.
-     */
-    public char skipTo(char to) {
-        char c;
-        int index = this.myIndex;
-        do {
-            c = next();
-            if (c == 0) {
-                this.myIndex = index;
-                return c;
+            /*
+             * Expect the name/value separator to be either a colon ':', an
+             * equals sign '=', or an arrow "=>". The last two are bogus but we
+             * include them because that's what the original implementation did.
+             */
+            int separator = nextCleanInternal();
+            if (separator != ':' && separator != '=') {
+                throw syntaxError("Expected ':' after " + name);
             }
-        } while (c != to);
-        back();
-        return c;
-    }
+            if (pos < in.length() && in.charAt(pos) == '>') {
+                pos++;
+            }
 
+            result.put((String) name, nextValue());
 
-    /**
-     * Skip characters until past the requested string.
-     * If it is not found, we are left at the end of the source.
-     * @param to A string to skip past.
-     */
-    public void skipPast(String to) {
-        this.myIndex = this.mySource.indexOf(to, this.myIndex);
-        if (this.myIndex < 0) {
-            this.myIndex = this.mySource.length();
-        } else {
-            this.myIndex += to.length();
+            switch (nextCleanInternal()) {
+                case '}':
+                    return result;
+                case ';':
+                case ',':
+                    continue;
+                default:
+                    throw syntaxError("Unterminated object");
+            }
         }
     }
 
+    /**
+     * Reads a sequence of values and the trailing closing brace ']' of an
+     * array. The opening brace '[' should have already been read. Note that
+     * "[]" yields an empty array, but "[,]" returns a two-element array
+     * equivalent to "[null,null]".
+     */
+    private JSONArray readArray() throws JSONException {
+        JSONArray result = new JSONArray();
+
+        /* to cover input that ends with ",]". */
+        boolean hasTrailingSeparator = false;
+
+        while (true) {
+            switch (nextCleanInternal()) {
+                case -1:
+                    throw syntaxError("Unterminated array");
+                case ']':
+                    if (hasTrailingSeparator) {
+                        result.put(null);
+                    }
+                    return result;
+                case ',':
+                case ';':
+                    /* A separator without a value first means "null". */
+                    result.put(null);
+                    hasTrailingSeparator = true;
+                    continue;
+                default:
+                    pos--;
+            }
+
+            result.put(nextValue());
+
+            switch (nextCleanInternal()) {
+                case ']':
+                    return result;
+                case ',':
+                case ';':
+                    hasTrailingSeparator = true;
+                    continue;
+                default:
+                    throw syntaxError("Unterminated array");
+            }
+        }
+    }
 
     /**
-     * Make a JSONException to signal a syntax error.
-     *
-     * @param message The error message.
-     * @return  A JSONException object, suitable for throwing
+     * Returns an exception containing the given message plus the current
+     * position and the entire input string.
      */
     public JSONException syntaxError(String message) {
-        return new JSONException(message + toString());
+        return new JSONException(message + this);
     }
 
+    /**
+     * Returns the current position and the entire input string.
+     */
+    @Override public String toString() {
+        // consistent with the original implementation
+        return " at character " + pos + " of " + in;
+    }
+
+    /*
+     * Legacy APIs.
+     *
+     * None of the methods below are on the critical path of parsing JSON
+     * documents. They exist only because they were exposed by the original
+     * implementation and may be used by some clients.
+     */
 
     /**
-     * Make a printable string of this JSONTokener.
-     *
-     * @return " at character [this.myIndex] of [this.mySource]"
+     * Returns true until the input has been exhausted.
      */
-    public String toString() {
-        return " at character " + this.myIndex + " of " + this.mySource;
-    }   
-}
\ No newline at end of file
+    public boolean more() {
+        return pos < in.length();
+    }
+
+    /**
+     * Returns the next available character, or the null character '\0' if all
+     * input has been exhausted. The return value of this method is ambiguous
+     * for JSON strings that contain the character '\0'.
+     */
+    public char next() {
+        return pos < in.length() ? in.charAt(pos++) : '\0';
+    }
+
+    /**
+     * Returns the next available character if it equals {@code c}. Otherwise an
+     * exception is thrown.
+     */
+    public char next(char c) throws JSONException {
+        char result = next();
+        if (result != c) {
+            throw syntaxError("Expected " + c + " but was " + result);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the next character that is not whitespace and does not belong to
+     * a comment. If the input is exhausted before such a character can be
+     * found, the null character '\0' is returned. The return value of this
+     * method is ambiguous for JSON strings that contain the character '\0'.
+     */
+    public char nextClean() throws JSONException {
+        int nextCleanInt = nextCleanInternal();
+        return nextCleanInt == -1 ? '\0' : (char) nextCleanInt;
+    }
+
+    /**
+     * Returns the next {@code length} characters of the input.
+     *
+     * <p>The returned string shares its backing character array with this
+     * tokener's input string. If a reference to the returned string may be held
+     * indefinitely, you should use {@code new String(result)} to copy it first
+     * to avoid memory leaks.
+     *
+     * @throws JSONException if the remaining input is not long enough to
+     *     satisfy this request.
+     */
+    public String next(int length) throws JSONException {
+        if (pos + length > in.length()) {
+            throw syntaxError(length + " is out of bounds");
+        }
+        String result = in.substring(pos, pos + length);
+        pos += length;
+        return result;
+    }
+
+    /**
+     * Returns the {@link String#trim trimmed} string holding the characters up
+     * to but not including the first of:
+     * <ul>
+     *   <li>any character in {@code excluded}
+     *   <li>a newline character '\n'
+     *   <li>a carriage return '\r'
+     * </ul>
+     *
+     * <p>The returned string shares its backing character array with this
+     * tokener's input string. If a reference to the returned string may be held
+     * indefinitely, you should use {@code new String(result)} to copy it first
+     * to avoid memory leaks.
+     *
+     * @return a possibly-empty string
+     */
+    public String nextTo(String excluded) {
+        if (excluded == null) {
+            throw new NullPointerException();
+        }
+        return nextToInternal(excluded).trim();
+    }
+
+    /**
+     * Equivalent to {@code nextTo(String.valueOf(excluded))}.
+     */
+    public String nextTo(char excluded) {
+        return nextToInternal(String.valueOf(excluded)).trim();
+    }
+
+    /**
+     * Advances past all input up to and including the next occurrence of
+     * {@code thru}. If the remaining input doesn't contain {@code thru}, the
+     * input is exhausted.
+     */
+    public void skipPast(String thru) {
+        int thruStart = in.indexOf(thru, pos);
+        pos = thruStart == -1 ? in.length() : (thruStart + thru.length());
+    }
+
+    /**
+     * Advances past all input up to but not including the next occurrence of
+     * {@code to}. If the remaining input doesn't contain {@code to}, the input
+     * is unchanged.
+     */
+    public char skipTo(char to) {
+        int index = in.indexOf(to, pos);
+        if (index != -1) {
+            pos = index;
+            return to;
+        } else {
+            return '\0';
+        }
+    }
+
+    /**
+     * Unreads the most recent character of input. If no input characters have
+     * been read, the input is unchanged.
+     */
+    public void back() {
+        if (--pos == -1) {
+            pos = 0;
+        }
+    }
+
+    /**
+     * Returns the integer [0..15] value for the given hex character, or -1
+     * for non-hex input.
+     *
+     * @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other
+     *     character will yield a -1 result.
+     */
+    public static int dehexchar(char hex) {
+        if (hex >= '0' && hex <= '9') {
+            return hex - '0';
+        } else if (hex >= 'A' && hex <= 'F') {
+            return hex - 'A' + 10;
+        } else if (hex >= 'a' && hex <= 'f') {
+            return hex - 'a' + 10;
+        } else {
+            return -1;
+        }
+    }
+}
diff --git a/json/src/test/java/org/json/AllTests.java b/json/src/test/java/org/json/AllTests.java
index ee6c90e..8f5cc94 100644
--- a/json/src/test/java/org/json/AllTests.java
+++ b/json/src/test/java/org/json/AllTests.java
@@ -26,6 +26,7 @@
         suite.addTestSuite(JSONObjectTest.class);
         suite.addTestSuite(JSONStringerTest.class);
         suite.addTestSuite(JSONTokenerTest.class);
+        suite.addTestSuite(ParsingTest.class);
         suite.addTestSuite(SelfUseTest.class);
         return suite;
     }
diff --git a/json/src/test/java/org/json/JSONArrayTest.java b/json/src/test/java/org/json/JSONArrayTest.java
index d6013a8..09990b9 100644
--- a/json/src/test/java/org/json/JSONArrayTest.java
+++ b/json/src/test/java/org/json/JSONArrayTest.java
@@ -1,11 +1,11 @@
-/**
+/*
  * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -20,6 +20,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Collections;
 
 /**
  * This black box test was written without inspecting the non-free org.json sourcecode.
@@ -50,7 +51,7 @@
         assertFalse(array.optBoolean(0));
         assertTrue(array.optBoolean(0, true));
 
-        // bogus (but documented) behaviour: returns null rather than an empty object
+        // bogus (but documented) behaviour: returns null rather than an empty object!
         assertNull(array.toJSONObject(new JSONArray()));
     }
 
@@ -58,8 +59,7 @@
         JSONArray a = new JSONArray();
         JSONArray b = new JSONArray();
         assertTrue(a.equals(b));
-        // bogus behavior: JSONArray overrides equals() but not hashCode().
-        assertEquals(a.hashCode(), b.hashCode());
+        assertEquals("equals() not consistent with hashCode()", a.hashCode(), b.hashCode());
 
         a.put(true);
         a.put(false);
@@ -129,7 +129,7 @@
         assertEquals(4, array.length());
         assertEquals("[null,null,null,null]", array.toString());
 
-        // bogus behaviour: there's 2 ways to represent null; each behaves differently!
+        // there's 2 ways to represent null; each behaves differently!
         assertEquals(JSONObject.NULL, array.get(0));
         try {
             array.get(1);
@@ -160,6 +160,29 @@
         assertEquals("", array.optString(3));
     }
 
+    /**
+     * Our behaviour is questioned by this bug:
+     * http://code.google.com/p/android/issues/detail?id=7257
+     */
+    public void testParseNullYieldsJSONObjectNull() throws JSONException {
+        JSONArray array = new JSONArray("[\"null\",null]");
+        array.put(null);
+        assertEquals("null", array.get(0));
+        assertEquals(JSONObject.NULL, array.get(1));
+        try {
+            array.get(2);
+            fail();
+        } catch (JSONException e) {
+        }
+        assertEquals("null", array.getString(0));
+        assertEquals("null", array.getString(1));
+        try {
+            array.getString(2);
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
     public void testNumbers() throws JSONException {
         JSONArray array = new JSONArray();
         array.put(Double.MIN_VALUE);
@@ -168,7 +191,7 @@
         array.put(-0d);
         assertEquals(4, array.length());
 
-        // bogus behaviour: toString() and getString(int) return different values for -0d
+        // toString() and getString(int) return different values for -0d
         assertEquals("[4.9E-324,9223372036854775806,1.7976931348623157E308,-0]", array.toString());
 
         assertEquals(Double.MIN_VALUE, array.get(0));
@@ -259,6 +282,32 @@
         assertEquals(-1.0d, array.optDouble(3, -1.0d));
     }
 
+    public void testJoin() throws JSONException {
+        JSONArray array = new JSONArray();
+        array.put(null);
+        assertEquals("null", array.join(" & "));
+        array.put("\"");
+        assertEquals("null & \"\\\"\"", array.join(" & "));
+        array.put(5);
+        assertEquals("null & \"\\\"\" & 5", array.join(" & "));
+        array.put(true);
+        assertEquals("null & \"\\\"\" & 5 & true", array.join(" & "));
+        array.put(new JSONArray(Arrays.asList(true, false)));
+        assertEquals("null & \"\\\"\" & 5 & true & [true,false]", array.join(" & "));
+        array.put(new JSONObject(Collections.singletonMap("x", 6)));
+        assertEquals("null & \"\\\"\" & 5 & true & [true,false] & {\"x\":6}", array.join(" & "));
+    }
+
+    public void testJoinWithNull() throws JSONException {
+        JSONArray array = new JSONArray(Arrays.asList(5, 6));
+        assertEquals("5null6", array.join(null));
+    }
+
+    public void testJoinWithSpecialCharacters() throws JSONException {
+        JSONArray array = new JSONArray(Arrays.asList(5, 6));
+        assertEquals("5\"6", array.join("\""));
+    }
+
     public void testToJSONObject() throws JSONException {
         JSONArray keys = new JSONArray();
         keys.put("a");
@@ -286,7 +335,7 @@
         values.put(5.5d);
         values.put(null);
 
-        // bogus behaviour: null values are stripped
+        // null values are stripped!
         JSONObject object = values.toJSONObject(keys);
         assertEquals(1, object.length());
         assertFalse(object.has("b"));
@@ -345,6 +394,14 @@
         }
     }
 
+    public void testPutUnsupportedNumbersAsObject() throws JSONException {
+        JSONArray array = new JSONArray();
+        array.put(Double.valueOf(Double.NaN));
+        array.put(Double.valueOf(Double.NEGATIVE_INFINITY));
+        array.put(Double.valueOf(Double.POSITIVE_INFINITY));
+        assertEquals(null, array.toString());
+    }
+
     /**
      * Although JSONArray is usually defensive about which numbers it accepts,
      * it doesn't check inputs in its constructor.
@@ -357,7 +414,7 @@
     }
 
     public void testToStringWithUnsupportedNumbers() throws JSONException {
-        // bogus behaviour: when the array contains an unsupported number, toString returns null
+        // when the array contains an unsupported number, toString returns null!
         JSONArray array = new JSONArray(Arrays.asList(5.5, Double.NaN));
         assertNull(array.toString());
     }
@@ -369,6 +426,70 @@
         assertEquals(5, array.get(0));
     }
 
+    public void testTokenerConstructor() throws JSONException {
+        JSONArray object = new JSONArray(new JSONTokener("[false]"));
+        assertEquals(1, object.length());
+        assertEquals(false, object.get(0));
+    }
+
+    public void testTokenerConstructorWrongType() throws JSONException {
+        try {
+            new JSONArray(new JSONTokener("{\"foo\": false}"));
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testTokenerConstructorNull() throws JSONException {
+        try {
+            new JSONArray((JSONTokener) null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testTokenerConstructorParseFail() {
+        try {
+            new JSONArray(new JSONTokener("["));
+            fail();
+        } catch (JSONException e) {
+        } catch (StackOverflowError e) {
+            fail("Stack overflowed on input: \"[\"");
+        }
+    }
+
+    public void testStringConstructor() throws JSONException {
+        JSONArray object = new JSONArray("[false]");
+        assertEquals(1, object.length());
+        assertEquals(false, object.get(0));
+    }
+
+    public void testStringConstructorWrongType() throws JSONException {
+        try {
+            new JSONArray("{\"foo\": false}");
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testStringConstructorNull() throws JSONException {
+        try {
+            new JSONArray((String) null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testStringConstructorParseFail() {
+        try {
+            new JSONArray("[");
+            fail();
+        } catch (JSONException e) {
+        } catch (StackOverflowError e) {
+            fail("Stack overflowed on input: \"[\"");
+        }
+    }
+
     public void testCreate() throws JSONException {
         JSONArray array = new JSONArray(Arrays.asList(5.5, true));
         assertEquals(2, array.length());
@@ -405,8 +526,4 @@
         } catch (JSONException e) {
         }
     }
-
-    public void testParsingConstructor() {
-        fail("TODO");
-    }
 }
diff --git a/json/src/test/java/org/json/JSONObjectTest.java b/json/src/test/java/org/json/JSONObjectTest.java
index 83beea8..b896a7f 100644
--- a/json/src/test/java/org/json/JSONObjectTest.java
+++ b/json/src/test/java/org/json/JSONObjectTest.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (C) 2010 Google Inc.
+/*
+ * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-
 package org.json;
 
 import junit.framework.TestCase;
@@ -32,10 +31,10 @@
         JSONObject object = new JSONObject();
         assertEquals(0, object.length());
 
-        // bogus (but documented) behaviour: returns null rather than the empty object
+        // bogus (but documented) behaviour: returns null rather than the empty object!
         assertNull(object.names());
 
-        // bogus behaviour: returns null rather than an empty array
+        // returns null rather than an empty array!
         assertNull(object.toJSONArray(new JSONArray()));
         assertEquals("{}", object.toString());
         assertEquals("{}", object.toString(5));
@@ -246,13 +245,14 @@
         object.put("quux", -0d);
         assertEquals(4, object.length());
 
-        assertTrue(object.toString().contains("\"foo\":4.9E-324"));
-        assertTrue(object.toString().contains("\"bar\":9223372036854775806"));
-        assertTrue(object.toString().contains("\"baz\":1.7976931348623157E308"));
+        String toString = object.toString();
+        assertTrue(toString, toString.contains("\"foo\":4.9E-324"));
+        assertTrue(toString, toString.contains("\"bar\":9223372036854775806"));
+        assertTrue(toString, toString.contains("\"baz\":1.7976931348623157E308"));
 
-        // bogus behaviour: toString() and getString() return different values for -0d
-        assertTrue(object.toString().contains("\"quux\":-0}") // no trailing decimal point
-                || object.toString().contains("\"quux\":-0,"));
+        // toString() and getString() return different values for -0d!
+        assertTrue(toString, toString.contains("\"quux\":-0}") // no trailing decimal point
+                || toString.contains("\"quux\":-0,"));
 
         assertEquals(Double.MIN_VALUE, object.get("foo"));
         assertEquals(9223372036854775806L, object.get("bar"));
@@ -310,13 +310,13 @@
     public void testOtherNumbers() throws JSONException {
         Number nan = new Number() {
             public int intValue() {
-                return 0;
+                throw new UnsupportedOperationException();
             }
             public long longValue() {
-                return 1L;
+                throw new UnsupportedOperationException();
             }
             public float floatValue() {
-                return 2;
+                throw new UnsupportedOperationException();
             }
             public double doubleValue() {
                 return Double.NaN;
@@ -326,10 +326,12 @@
             }
         };
 
-        // bogus behaviour: foreign object types should be rejected!
         JSONObject object = new JSONObject();
-        object.put("foo", nan);
-        assertEquals("{\"foo\":x}", object.toString());
+        try {
+            object.put("foo", nan);
+            fail("Object.put() accepted a NaN (via a custom Number class)");
+        } catch (JSONException e) {
+        }
     }
 
     public void testForeignObjects() throws JSONException {
@@ -339,7 +341,7 @@
             }
         };
 
-        // bogus behaviour: foreign object types should be rejected and not treated as Strings
+        // foreign object types are accepted and treated as Strings!
         JSONObject object = new JSONObject();
         object.put("foo", foreign);
         assertEquals("{\"foo\":\"x\"}", object.toString());
@@ -451,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();
@@ -527,7 +576,7 @@
         names.put(false);
         names.put("foo");
 
-        // bogus behaviour: array elements are converted to Strings
+        // array elements are converted to strings to do name lookups on the map!
         JSONArray array = object.toJSONArray(names);
         assertEquals(3, array.length());
         assertEquals(10, array.get(0));
@@ -590,7 +639,7 @@
     }
 
     public void testToStringWithUnsupportedNumbers() {
-        // bogus behaviour: when the object contains an unsupported number, toString returns null
+        // when the object contains an unsupported number, toString returns null!
         JSONObject object = new JSONObject(Collections.singletonMap("foo", Double.NaN));
         assertEquals(null, object.toString());
     }
@@ -607,8 +656,97 @@
         Map<Object, Object> contents = new HashMap<Object, Object>();
         contents.put(5, 5);
 
-        // bogus behaviour: the constructor doesn't validate its input
-        new JSONObject(contents);
+        try {
+            new JSONObject(contents);
+            fail("JSONObject constructor doesn't validate its input!");
+        } catch (Exception e) {
+        }
+    }
+
+    public void testTokenerConstructor() throws JSONException {
+        JSONObject object = new JSONObject(new JSONTokener("{\"foo\": false}"));
+        assertEquals(1, object.length());
+        assertEquals(false, object.get("foo"));
+    }
+
+    public void testTokenerConstructorWrongType() throws JSONException {
+        try {
+            new JSONObject(new JSONTokener("[\"foo\", false]"));
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testTokenerConstructorNull() throws JSONException {
+        try {
+            new JSONObject((JSONTokener) null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testTokenerConstructorParseFail() {
+        try {
+            new JSONObject(new JSONTokener("{"));
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testStringConstructor() throws JSONException {
+        JSONObject object = new JSONObject("{\"foo\": false}");
+        assertEquals(1, object.length());
+        assertEquals(false, object.get("foo"));
+    }
+
+    public void testStringConstructorWrongType() throws JSONException {
+        try {
+            new JSONObject("[\"foo\", false]");
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testStringConstructorNull() throws JSONException {
+        try {
+            new JSONObject((String) null);
+            fail();
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testStringonstructorParseFail() {
+        try {
+            new JSONObject("{");
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testCopyConstructor() throws JSONException {
+        JSONObject source = new JSONObject();
+        source.put("a", JSONObject.NULL);
+        source.put("b", false);
+        source.put("c", 5);
+
+        JSONObject copy = new JSONObject(source, new String[] { "a", "c" });
+        assertEquals(2, copy.length());
+        assertEquals(JSONObject.NULL, copy.get("a"));
+        assertEquals(5, copy.get("c"));
+        assertEquals(null, copy.opt("b"));
+    }
+
+    public void testCopyConstructorMissingName() throws JSONException {
+        JSONObject source = new JSONObject();
+        source.put("a", JSONObject.NULL);
+        source.put("b", false);
+        source.put("c", 5);
+
+        JSONObject copy = new JSONObject(source, new String[]{ "a", "c", "d" });
+        assertEquals(2, copy.length());
+        assertEquals(JSONObject.NULL, copy.get("a"));
+        assertEquals(5, copy.get("c"));
+        assertEquals(0, copy.optInt("b"));
     }
 
     public void testAccumulateMutatesInPlace() throws JSONException {
@@ -658,7 +796,7 @@
         object.put("foo", JSONObject.NULL);
         object.put("bar", null);
 
-        // bogus behaviour: there's 2 ways to represent null; each behaves differently!
+        // there are two ways to represent null; each behaves differently!
         assertTrue(object.has("foo"));
         assertFalse(object.has("bar"));
         assertTrue(object.isNull("foo"));
@@ -767,7 +905,11 @@
     }
 
     public void testQuote() {
-        // covered by JSONStringerTest.testEscaping()
+        // covered by JSONStringerTest.testEscaping
+    }
+
+    public void testQuoteNull() throws JSONException {
+        assertEquals("\"\"", JSONObject.quote(null));
     }
 
     public void testNumberToString() throws JSONException {
diff --git a/json/src/test/java/org/json/JSONStringerTest.java b/json/src/test/java/org/json/JSONStringerTest.java
index 03a2903..64c3a74 100644
--- a/json/src/test/java/org/json/JSONStringerTest.java
+++ b/json/src/test/java/org/json/JSONStringerTest.java
@@ -1,11 +1,11 @@
-/**
+/*
  * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,7 +24,7 @@
 public class JSONStringerTest extends TestCase {
 
     public void testEmptyStringer() {
-        // bogus behaviour: why isn't this the empty string?
+        // why isn't this the empty string?
         assertNull(new JSONStringer().toString());
     }
 
@@ -62,6 +62,16 @@
         assertEquals("[false,5,5,\"five\",null]", stringer.toString());
     }
 
+    public void testValueObjectMethods() throws JSONException {
+        JSONStringer stringer = new JSONStringer();
+        stringer.array();
+        stringer.value(Boolean.FALSE);
+        stringer.value(Double.valueOf(5.0));
+        stringer.value(Long.valueOf(5L));
+        stringer.endArray();
+        assertEquals("[false,5,5]", stringer.toString());
+    }
+
     public void testKeyValue() throws JSONException {
         JSONStringer stringer = new JSONStringer();
         stringer.object();
@@ -187,26 +197,27 @@
 
     public void testEscaping() throws JSONException {
         assertEscapedAllWays("a", "a");
-        assertEscapedAllWays("a\"", "a\\\"");
-        assertEscapedAllWays("\"", "\\\"");
+        assertEscapedAllWays("a\\\"", "a\"");
+        assertEscapedAllWays("\\\"", "\"");
         assertEscapedAllWays(":", ":");
         assertEscapedAllWays(",", ",");
-        assertEscapedAllWays("\n", "\\n");
-        assertEscapedAllWays("\t", "\\t");
+        assertEscapedAllWays("\\b", "\b");
+        assertEscapedAllWays("\\f", "\f");
+        assertEscapedAllWays("\\n", "\n");
+        assertEscapedAllWays("\\r", "\r");
+        assertEscapedAllWays("\\t", "\t");
         assertEscapedAllWays(" ", " ");
-        assertEscapedAllWays("\\", "\\\\");
+        assertEscapedAllWays("\\\\", "\\");
         assertEscapedAllWays("{", "{");
         assertEscapedAllWays("}", "}");
         assertEscapedAllWays("[", "[");
         assertEscapedAllWays("]", "]");
-
-        // how does it decide which characters to escape?
-        assertEscapedAllWays("\0", "\\u0000");
-        assertEscapedAllWays("\u0019", "\\u0019");
-        assertEscapedAllWays("\u0020", " ");
+        assertEscapedAllWays("\\u0000", "\0");
+        assertEscapedAllWays("\\u0019", "\u0019");
+        assertEscapedAllWays(" ", "\u0020");
     }
 
-    private void assertEscapedAllWays(String original, String escaped) throws JSONException {
+    private void assertEscapedAllWays(String escaped, String original) throws JSONException {
         assertEquals("{\"" + escaped + "\":false}",
                 new JSONStringer().object().key(original).value(false).endObject().toString());
         assertEquals("{\"a\":\"" + escaped + "\"}",
@@ -236,7 +247,7 @@
         assertEquals("{\"b\":{\"a\":false}}", stringer.toString());
     }
 
-    public void testArrayNestingMaxDepthIs20() throws JSONException {
+    public void testArrayNestingMaxDepthSupports20() throws JSONException {
         JSONStringer stringer = new JSONStringer();
         for (int i = 0; i < 20; i++) {
             stringer.array();
@@ -250,14 +261,9 @@
         for (int i = 0; i < 20; i++) {
             stringer.array();
         }
-        try {
-            stringer.array();
-            fail();
-        } catch (JSONException e) {
-        }
     }
 
-    public void testObjectNestingMaxDepthIs20() throws JSONException {
+    public void testObjectNestingMaxDepthSupports20() throws JSONException {
         JSONStringer stringer = new JSONStringer();
         for (int i = 0; i < 20; i++) {
             stringer.object();
@@ -276,14 +282,9 @@
             stringer.object();
             stringer.key("a");
         }
-        try {
-            stringer.object();
-            fail();
-        } catch (JSONException e) {
-        }
     }
 
-    public void testMixedMaxDepth() throws JSONException {
+    public void testMixedMaxDepthSupports20() throws JSONException {
         JSONStringer stringer = new JSONStringer();
         for (int i = 0; i < 20; i+=2) {
             stringer.array();
@@ -305,11 +306,6 @@
             stringer.object();
             stringer.key("a");
         }
-        try {
-            stringer.array();
-            fail();
-        } catch (JSONException e) {
-        }
     }
 
     public void testMaxDepthWithArrayValue() throws JSONException {
diff --git a/json/src/test/java/org/json/JSONTokenerTest.java b/json/src/test/java/org/json/JSONTokenerTest.java
index 1409a3b..0d4f9d3 100644
--- a/json/src/test/java/org/json/JSONTokenerTest.java
+++ b/json/src/test/java/org/json/JSONTokenerTest.java
@@ -1,11 +1,11 @@
-/**
+/*
  * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -17,6 +17,7 @@
 package org.json;
 
 import junit.framework.TestCase;
+import junit.framework.AssertionFailedError;
 
 /**
  * This black box test was written without inspecting the non-free org.json sourcecode.
@@ -24,7 +25,7 @@
 public class JSONTokenerTest extends TestCase {
 
     public void testNulls() throws JSONException {
-        // bogus behaviour: JSONTokener accepts null, only to fail later on almost all APIs.
+        // JSONTokener accepts null, only to fail later on almost all APIs!
         new JSONTokener(null).back();
 
         try {
@@ -147,12 +148,6 @@
         }
         assertEquals('E', abcdeTokener.nextClean());
         assertEquals('\0', abcdeTokener.next());
-        try {
-            // bogus behaviour: returning an empty string should be valid
-            abcdeTokener.next(0);
-            fail();
-        } catch (JSONException e) {
-        }
         assertFalse(abcdeTokener.more());
         abcdeTokener.back();
         assertTrue(abcdeTokener.more());
@@ -174,7 +169,7 @@
         abcTokener.back();
         abcTokener.back();
         abcTokener.back();
-        abcTokener.back(); // bogus behaviour: you can back up before the beginning of a String
+        abcTokener.back(); // you can back up before the beginning of a String!
         assertEquals('A', abcTokener.next());
     }
 
@@ -209,26 +204,31 @@
             fail();
         } catch (JSONException e) {
         }
+    }
+
+    public void testNextNWithAllRemaining() throws JSONException {
+        JSONTokener tokener = new JSONTokener("ABCDEF");
+        tokener.next(3);
         try {
-            // bogus behaviour: there should be 3 characters left, but there must be an off-by-one
-            // error in the implementation.
-            assertEquals("DEF", abcdeTokener.next(3));
-            fail();
+            tokener.next(3);
         } catch (JSONException e) {
+            AssertionFailedError error = new AssertionFailedError("off-by-one error?");
+            error.initCause(e);
+            throw error;
         }
-        assertEquals("DE", abcdeTokener.next(2));
-        assertEquals('F', abcdeTokener.next());
+    }
+
+    public void testNext0() throws JSONException {
+        JSONTokener tokener = new JSONTokener("ABCDEF");
+        tokener.next(5);
+        tokener.next();
         try {
-            // bogus behaviour: returning an empty string should be valid
-            abcdeTokener.next(0);
-            fail();
+            tokener.next(0);
         } catch (JSONException e) {
+            Error error = new AssertionFailedError("Returning an empty string should be valid");
+            error.initCause(e);
+            throw error;
         }
-        abcdeTokener.back();
-        abcdeTokener.back();
-        abcdeTokener.back();
-        assertEquals("DE", abcdeTokener.next(2));
-        assertEquals('F', abcdeTokener.next());
     }
 
     public void testNextCleanComments() throws JSONException {
@@ -241,6 +241,35 @@
         assertEquals('\0', tokener.nextClean());
     }
 
+    public void testNextCleanNestedCStyleComments() throws JSONException {
+        JSONTokener tokener = new JSONTokener("A /* B /* C */ D */ E");
+        assertEquals('A', tokener.nextClean());
+        assertEquals('D', tokener.nextClean());
+        assertEquals('*', tokener.nextClean());
+        assertEquals('/', tokener.nextClean());
+        assertEquals('E', tokener.nextClean());
+    }
+
+    /**
+     * Some applications rely on parsing '#' to lead an end-of-line comment.
+     * http://b/2571423
+     */
+    public void testNextCleanHashComments() throws JSONException {
+        JSONTokener tokener = new JSONTokener("A # B */ /* C */ \nD #");
+        assertEquals('A', tokener.nextClean());
+        assertEquals('D', tokener.nextClean());
+        assertEquals('\0', tokener.nextClean());
+    }
+
+    public void testNextCleanCommentsTrailingSingleSlash() throws JSONException {
+        JSONTokener tokener = new JSONTokener(" / S /");
+        assertEquals('/', tokener.nextClean());
+        assertEquals('S', tokener.nextClean());
+        assertEquals('/', tokener.nextClean());
+        assertEquals("nextClean doesn't consume a trailing slash",
+                '\0', tokener.nextClean());
+    }
+
     public void testNextCleanTrailingOpenComment() throws JSONException {
         try {
             new JSONTokener("  /* ").nextClean();
@@ -256,55 +285,54 @@
         assertEquals('B', new JSONTokener("  // \r  B ").nextClean());
     }
 
+    public void testNextCleanSkippedWhitespace() throws JSONException {
+        assertEquals("character tabulation", 'A', new JSONTokener("\tA").nextClean());
+        assertEquals("line feed",            'A', new JSONTokener("\nA").nextClean());
+        assertEquals("carriage return",      'A', new JSONTokener("\rA").nextClean());
+        assertEquals("space",                'A', new JSONTokener(" A").nextClean());
+    }
+
     /**
      * Tests which characters tokener treats as ignorable whitespace. See Kevin Bourrillion's
      * <a href="https://spreadsheets.google.com/pub?key=pd8dAQyHbdewRsnE5x5GzKQ">list
      * of whitespace characters</a>.
      */
-    public void testNextCleanWhitespace() throws JSONException {
-        // This behaviour contradicts the JSON spec. It claims the only space
-        // characters are space, tab, newline and carriage return. But it treats
-        // many characters like whitespace! These are the same whitespace
-        // characters used by String.trim(), with the exception of '\0'.
-        assertEquals("character tabulation",      'A', new JSONTokener("\u0009A").nextClean());
-        assertEquals("line feed",                 'A', new JSONTokener("\nA").nextClean());
-        assertEquals("line tabulation",           'A', new JSONTokener("\u000bA").nextClean());
-        assertEquals("form feed",                 'A', new JSONTokener("\u000cA").nextClean());
-        assertEquals("carriage return",           'A', new JSONTokener("\rA").nextClean());
-        assertEquals("information separator 4",   'A', new JSONTokener("\u001cA").nextClean());
-        assertEquals("information separator 3",   'A', new JSONTokener("\u001dA").nextClean());
-        assertEquals("information separator 2",   'A', new JSONTokener("\u001eA").nextClean());
-        assertEquals("information separator 1",   'A', new JSONTokener("\u001fA").nextClean());
-        assertEquals("space",                     'A', new JSONTokener("\u0020A").nextClean());
-        for (char c = '\u0002'; c < ' '; c++) {
-            assertEquals('A', new JSONTokener(new String(new char[] { ' ', c, 'A' })).nextClean());
-        }
+    public void testNextCleanRetainedWhitespace() throws JSONException {
+        assertNotClean("null",                      '\u0000');
+        assertNotClean("next line",                 '\u0085');
+        assertNotClean("non-breaking space",        '\u00a0');
+        assertNotClean("ogham space mark",          '\u1680');
+        assertNotClean("mongolian vowel separator", '\u180e');
+        assertNotClean("en quad",                   '\u2000');
+        assertNotClean("em quad",                   '\u2001');
+        assertNotClean("en space",                  '\u2002');
+        assertNotClean("em space",                  '\u2003');
+        assertNotClean("three-per-em space",        '\u2004');
+        assertNotClean("four-per-em space",         '\u2005');
+        assertNotClean("six-per-em space",          '\u2006');
+        assertNotClean("figure space",              '\u2007');
+        assertNotClean("punctuation space",         '\u2008');
+        assertNotClean("thin space",                '\u2009');
+        assertNotClean("hair space",                '\u200a');
+        assertNotClean("zero-width space",          '\u200b');
+        assertNotClean("left-to-right mark",        '\u200e');
+        assertNotClean("right-to-left mark",        '\u200f');
+        assertNotClean("line separator",            '\u2028');
+        assertNotClean("paragraph separator",       '\u2029');
+        assertNotClean("narrow non-breaking space", '\u202f');
+        assertNotClean("medium mathematical space", '\u205f');
+        assertNotClean("ideographic space",         '\u3000');
+        assertNotClean("line tabulation",           '\u000b');
+        assertNotClean("form feed",                 '\u000c');
+        assertNotClean("information separator 4",   '\u001c');
+        assertNotClean("information separator 3",   '\u001d');
+        assertNotClean("information separator 2",   '\u001e');
+        assertNotClean("information separator 1",   '\u001f');
+    }
 
-        // These characters are neither whitespace in the JSON spec nor the implementation
-        assertEquals("null",                      '\u0000', new JSONTokener("\u0000A").nextClean());
-        assertEquals("next line",                 '\u0085', new JSONTokener("\u0085A").nextClean());
-        assertEquals("non-breaking space",        '\u00a0', new JSONTokener("\u00a0A").nextClean());
-        assertEquals("ogham space mark",          '\u1680', new JSONTokener("\u1680A").nextClean());
-        assertEquals("mongolian vowel separator", '\u180e', new JSONTokener("\u180eA").nextClean());
-        assertEquals("en quad",                   '\u2000', new JSONTokener("\u2000A").nextClean());
-        assertEquals("em quad",                   '\u2001', new JSONTokener("\u2001A").nextClean());
-        assertEquals("en space",                  '\u2002', new JSONTokener("\u2002A").nextClean());
-        assertEquals("em space",                  '\u2003', new JSONTokener("\u2003A").nextClean());
-        assertEquals("three-per-em space",        '\u2004', new JSONTokener("\u2004A").nextClean());
-        assertEquals("four-per-em space",         '\u2005', new JSONTokener("\u2005A").nextClean());
-        assertEquals("six-per-em space",          '\u2006', new JSONTokener("\u2006A").nextClean());
-        assertEquals("figure space",              '\u2007', new JSONTokener("\u2007A").nextClean());
-        assertEquals("punctuation space",         '\u2008', new JSONTokener("\u2008A").nextClean());
-        assertEquals("thin space",                '\u2009', new JSONTokener("\u2009A").nextClean());
-        assertEquals("hair space",                '\u200a', new JSONTokener("\u200aA").nextClean());
-        assertEquals("zero-width space",          '\u200b', new JSONTokener("\u200bA").nextClean());
-        assertEquals("left-to-right mark",        '\u200e', new JSONTokener("\u200eA").nextClean());
-        assertEquals("right-to-left mark",        '\u200f', new JSONTokener("\u200fA").nextClean());
-        assertEquals("line separator",            '\u2028', new JSONTokener("\u2028A").nextClean());
-        assertEquals("paragraph separator",       '\u2029', new JSONTokener("\u2029A").nextClean());
-        assertEquals("narrow non-breaking space", '\u202f', new JSONTokener("\u202fA").nextClean());
-        assertEquals("medium mathematical space", '\u205f', new JSONTokener("\u205fA").nextClean());
-        assertEquals("ideographic space",         '\u3000', new JSONTokener("\u3000A").nextClean());
+    private void assertNotClean(String name, char c) throws JSONException {
+        assertEquals("The character " + name + " is not whitespace according to the JSON spec.",
+                c, new JSONTokener(new String(new char[] { c, 'A' })).nextClean());
     }
 
     public void testNextString() throws JSONException {
@@ -374,6 +402,7 @@
         try {
             new JSONTokener("abc\\u002\"").nextString('"');
             fail();
+        } catch (NumberFormatException e) {
         } catch (JSONException e) {
         }
         try {
@@ -433,15 +462,6 @@
         assertEquals("ABC", tokener.nextTo("\n"));
         assertEquals("", tokener.nextTo("\n"));
 
-        // Bogus behaviour: the tokener stops after \0 always
-        tokener = new JSONTokener(" \0\t \fABC \n DEF");
-        assertEquals("", tokener.nextTo("D"));
-        assertEquals('\t', tokener.next());
-        assertEquals("ABC", tokener.nextTo("D"));
-        tokener = new JSONTokener("ABC\0DEF");
-        assertEquals("ABC", tokener.nextTo("\0"));
-        assertEquals("DEF", tokener.nextTo("\0"));
-
         tokener = new JSONTokener("");
         try {
             tokener.nextTo(null);
@@ -450,6 +470,32 @@
         }
     }
 
+    public void testNextToTrimming() {
+        assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo("DE"));
+        assertEquals("ABC", new JSONTokener("\t ABC \tDEF").nextTo('D'));
+    }
+
+    public void testNextToTrailing() {
+        assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo("G"));
+        assertEquals("ABC DEF", new JSONTokener("\t ABC DEF \t").nextTo('G'));
+    }
+
+    public void testNextToDoesntStopOnNull() {
+        String message = "nextTo() shouldn't stop after \\0 characters";
+        JSONTokener tokener = new JSONTokener(" \0\t \fABC \n DEF");
+        assertEquals(message, "ABC", tokener.nextTo("D"));
+        assertEquals(message, '\n', tokener.next());
+        assertEquals(message, "", tokener.nextTo("D"));
+    }
+
+    public void testNextToConsumesNull() {
+        String message = "nextTo shouldn't consume \\0.";
+        JSONTokener tokener = new JSONTokener("ABC\0DEF");
+        assertEquals(message, "ABC", tokener.nextTo("\0"));
+        assertEquals(message, '\0', tokener.next());
+        assertEquals(message, "DEF", tokener.nextTo("\0"));
+    }
+
     public void testSkipPast() {
         JSONTokener tokener = new JSONTokener("ABCDEF");
         tokener.skipPast("ABC");
@@ -509,11 +555,6 @@
         tokener.skipTo('A');
         assertEquals('F', tokener.next());
 
-        tokener = new JSONTokener("ABC\0DEF");
-        tokener.skipTo('F');
-        // bogus behaviour: skipTo gives up when it sees '\0'
-        assertEquals('A', tokener.next());
-
         tokener = new JSONTokener("ABC\nDEF");
         tokener.skipTo('F');
         assertEquals('F', tokener.next());
@@ -527,6 +568,12 @@
         assertEquals('D', tokener.next());
     }
 
+    public void testSkipToStopsOnNull() {
+        JSONTokener tokener = new JSONTokener("ABC\0DEF");
+        tokener.skipTo('F');
+        assertEquals("skipTo shouldn't stop when it sees '\\0'", 'F', tokener.next());
+    }
+
     public void testDehexchar() {
         assertEquals( 0, JSONTokener.dehexchar('0'));
         assertEquals( 1, JSONTokener.dehexchar('1'));
@@ -558,8 +605,4 @@
             assertEquals("dehexchar " + c, -1, JSONTokener.dehexchar((char) c));
         }
     }
-
-    public void testNextValue() {
-        fail("TODO");
-    }
 }
diff --git a/json/src/test/java/org/json/ParsingTest.java b/json/src/test/java/org/json/ParsingTest.java
new file mode 100644
index 0000000..98d9069
--- /dev/null
+++ b/json/src/test/java/org/json/ParsingTest.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.json;
+
+import junit.framework.TestCase;
+import junit.framework.AssertionFailedError;
+
+import java.util.*;
+
+public class ParsingTest extends TestCase {
+
+    public void testParsingNoObjects() {
+        try {
+            new JSONTokener("").nextValue();
+            fail();
+        } catch (JSONException e) {
+        }
+    }
+
+    public void testParsingLiterals() throws JSONException {
+        assertParsed(Boolean.TRUE, "true");
+        assertParsed(Boolean.FALSE, "false");
+        assertParsed(JSONObject.NULL, "null");
+        assertParsed(JSONObject.NULL, "NULL");
+        assertParsed(Boolean.FALSE, "False");
+        assertParsed(Boolean.TRUE, "truE");
+    }
+
+    public void testParsingQuotedStrings() throws JSONException {
+        assertParsed("abc", "\"abc\"");
+        assertParsed("123", "\"123\"");
+        assertParsed("foo\nbar", "\"foo\\nbar\"");
+        assertParsed("foo bar", "\"foo\\u0020bar\"");
+        assertParsed("\"{}[]/\\:,=;#", "\"\\\"{}[]/\\\\:,=;#\"");
+    }
+
+    public void testParsingSingleQuotedStrings() throws JSONException {
+        assertParsed("abc", "'abc'");
+        assertParsed("123", "'123'");
+        assertParsed("foo\nbar", "'foo\\nbar'");
+        assertParsed("foo bar", "'foo\\u0020bar'");
+        assertParsed("\"{}[]/\\:,=;#", "'\\\"{}[]/\\\\:,=;#'");
+    }
+
+    public void testParsingUnquotedStrings() throws JSONException {
+        assertParsed("abc", "abc");
+        assertParsed("123abc", "123abc");
+        assertParsed("123e0x", "123e0x");
+        assertParsed("123e", "123e");
+        assertParsed("123ee21", "123ee21");
+        assertParsed("0xFFFFFFFFFFFFFFFFF", "0xFFFFFFFFFFFFFFFFF");
+    }
+
+    /**
+     * Unfortunately the original implementation attempts to figure out what
+     * Java number type best suits an input value.
+     */
+    public void testParsingNumbersThatAreBestRepresentedAsLongs() throws JSONException {
+        assertParsed(9223372036854775807L, "9223372036854775807");
+        assertParsed(9223372036854775806L, "9223372036854775806");
+        assertParsed(-9223372036854775808L, "-9223372036854775808");
+        assertParsed(-9223372036854775807L, "-9223372036854775807");
+    }
+
+    public void testParsingNumbersThatAreBestRepresentedAsIntegers() throws JSONException {
+        assertParsed(0, "0");
+        assertParsed(5, "5");
+        assertParsed(-2147483648, "-2147483648");
+        assertParsed(2147483647, "2147483647");
+    }
+
+    public void testParsingNegativeZero() throws JSONException {
+        assertParsed(0, "-0");
+    }
+
+    public void testParsingIntegersWithAdditionalPrecisionYieldDoubles() throws JSONException {
+        assertParsed(1d, "1.00");
+        assertParsed(1d, "1.0");
+        assertParsed(0d, "0.0");
+        assertParsed(-0d, "-0.0");
+    }
+
+    public void testParsingNumbersThatAreBestRepresentedAsDoubles() throws JSONException {
+        assertParsed(9.223372036854776E18, "9223372036854775808");
+        assertParsed(-9.223372036854776E18, "-9223372036854775809");
+        assertParsed(1.7976931348623157E308, "1.7976931348623157e308");
+        assertParsed(2.2250738585072014E-308, "2.2250738585072014E-308");
+        assertParsed(4.9E-324, "4.9E-324");
+        assertParsed(4.9E-324, "4.9e-324");
+    }
+
+    public void testParsingOctalNumbers() throws JSONException {
+        assertParsed(5, "05");
+        assertParsed(8, "010");
+        assertParsed(1046, "02026");
+    }
+
+    public void testParsingHexNumbers() throws JSONException {
+        assertParsed(5, "0x5");
+        assertParsed(16, "0x10");
+        assertParsed(8230, "0x2026");
+        assertParsed(180150010, "0xABCDEFA");
+        assertParsed(2077093803, "0x7BCDEFAB");
+    }
+
+    public void testParsingLargeHexValues() throws JSONException {
+        assertParsed(Integer.MAX_VALUE, "0x7FFFFFFF");
+        String message = "Hex values are parsed as Strings if their signed " +
+                "value is greater than Integer.MAX_VALUE.";
+        assertParsed(message, 0x80000000L, "0x80000000");
+    }
+
+    public void test64BitHexValues() throws JSONException {
+        assertParsed("Large hex longs shouldn't be yield ints or strings", 
+                -1L, "0xFFFFFFFFFFFFFFFF");
+    }
+
+    public void testParsingWithCommentsAndWhitespace() throws JSONException {
+        assertParsed("baz", "  // foo bar \n baz");
+        assertParsed("baz", "  // foo bar \r baz");
+        assertParsed("baz", "  // foo bar \r\n baz");
+        assertParsed("baz", "  # foo bar \n baz");
+        assertParsed("baz", "  # foo bar \r baz");
+        assertParsed("baz", "  # foo bar \r\n baz");
+        assertParsed(5, "  /* foo bar \n baz */ 5");
+        assertParsed(5, "  /* foo bar \n baz */ 5 // quux");
+        assertParsed(5, "  5   ");
+        assertParsed(5, "  5  \r\n\t ");
+        assertParsed(5, "\r\n\t   5 ");
+    }
+
+    public void testParsingArrays() throws JSONException {
+        assertParsed(array(), "[]");
+        assertParsed(array(5, 6, true), "[5,6,true]");
+        assertParsed(array(5, 6, array()), "[5,6,[]]");
+        assertParsed(array(5, 6, 7), "[5;6;7]");
+        assertParsed(array(5, 6, 7), "[5  , 6 \t; \r\n 7\n]");
+        assertParsed(array(5, 6, 7, null), "[5,6,7,]");
+        assertParsed(array(null, null), "[,]");
+        assertParsed(array(5, null, null, null, 5), "[5,,,,5]");
+        assertParsed(array(null, 5), "[,5]");
+        assertParsed(array(null, null, null), "[,,]");
+        assertParsed(array(null, null, null, 5), "[,,,5]");
+    }
+
+    public void testParsingObjects() throws JSONException {
+        assertParsed(object("foo", 5), "{\"foo\": 5}");
+        assertParsed(object("foo", 5), "{foo: 5}");
+        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5, \"bar\": \"baz\"}");
+        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\": 5; \"bar\": \"baz\"}");
+        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"= 5; \"bar\"= \"baz\"}");
+        assertParsed(object("foo", 5, "bar", "baz"), "{\"foo\"=> 5; \"bar\"=> \"baz\"}");
+        assertParsed(object("foo", object(), "bar", array()), "{\"foo\"=> {}; \"bar\"=> []}");
+        assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\": {\"foo\": [5, 6]}}");
+        assertParsed(object("foo", object("foo", array(5, 6))), "{\"foo\":\n\t{\t \"foo\":[5,\r6]}}");
+    }
+
+    public void testSyntaxProblemUnterminatedObject() {
+        assertParseFail("{");
+        assertParseFail("{\"foo\"");
+        assertParseFail("{\"foo\":");
+        assertParseFail("{\"foo\":bar");
+        assertParseFail("{\"foo\":bar,");
+        assertParseFail("{\"foo\":bar,\"baz\"");
+        assertParseFail("{\"foo\":bar,\"baz\":");
+        assertParseFail("{\"foo\":bar,\"baz\":true");
+        assertParseFail("{\"foo\":bar,\"baz\":true,");
+    }
+
+    public void testSyntaxProblemEmptyString() {
+        assertParseFail("");
+    }
+
+    public void testSyntaxProblemUnterminatedArray() {
+        assertParseFail("[");
+        assertParseFail("[,");
+        assertParseFail("[,,");
+        assertParseFail("[true");
+        assertParseFail("[true,");
+        assertParseFail("[true,,");
+    }
+
+    public void testSyntaxProblemMalformedObject() {
+        assertParseFail("{:}");
+        assertParseFail("{\"key\":}");
+        assertParseFail("{:true}");
+        assertParseFail("{\"key\":true:}");
+        assertParseFail("{null:true}");
+        assertParseFail("{true:true}");
+        assertParseFail("{0xFF:true}");
+    }
+
+    private void assertParseFail(String malformedJson) {
+        try {
+            new JSONTokener(malformedJson).nextValue();
+            fail("Successfully parsed: \"" + malformedJson + "\"");
+        } catch (JSONException e) {
+        } catch (StackOverflowError e) {
+            fail("Stack overflowed on input: \"" + malformedJson + "\"");
+        }
+    }
+
+    private JSONArray array(Object... elements) {
+        return new JSONArray(Arrays.asList(elements));
+    }
+
+    private JSONObject object(Object... keyValuePairs) throws JSONException {
+        JSONObject result = new JSONObject();
+        for (int i = 0; i < keyValuePairs.length; i+=2) {
+            result.put((String) keyValuePairs[i], keyValuePairs[i+1]);
+        }
+        return result;
+    }
+
+    private void assertParsed(String message, Object expected, String json) throws JSONException {
+        Object actual = new JSONTokener(json).nextValue();
+        actual = canonicalize(actual);
+        expected = canonicalize(expected);
+        assertEquals("For input \"" + json + "\" " + message, expected, actual);
+    }
+
+    private void assertParsed(Object expected, String json) throws JSONException {
+        assertParsed("", expected, json);
+    }
+
+    /**
+     * Since they don't implement equals or hashCode properly, this recursively
+     * replaces JSONObjects with an equivalent HashMap, and JSONArrays with the
+     * equivalent ArrayList.
+     */
+    private Object canonicalize(Object input) throws JSONException {
+        if (input instanceof JSONArray) {
+            JSONArray array = (JSONArray) input;
+            List<Object> result = new ArrayList<Object>();
+            for (int i = 0; i < array.length(); i++) {
+                result.add(canonicalize(array.opt(i)));
+            }
+            return result;
+        } else if (input instanceof JSONObject) {
+            JSONObject object = (JSONObject) input;
+            Map<String, Object> result = new HashMap<String, Object>();
+            for (Iterator<?> i = object.keys(); i.hasNext(); ) {
+                String key = (String) i.next();
+                result.put(key, canonicalize(object.get(key)));
+            }
+            return result;
+        } else {
+            return input;
+        }
+    }
+}
diff --git a/json/src/test/java/org/json/SelfUseTest.java b/json/src/test/java/org/json/SelfUseTest.java
index e3d428b..15a0824 100644
--- a/json/src/test/java/org/json/SelfUseTest.java
+++ b/json/src/test/java/org/json/SelfUseTest.java
@@ -1,11 +1,11 @@
-/**
+/*
  * Copyright (C) 2010 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -19,9 +19,9 @@
 import junit.framework.TestCase;
 
 /**
- * These tests checks self use; ie. when methods delegate to non-final methods.
- * For the most part we doesn't attempt to cover self-use, except in those cases
- * where our clean room implementation does it.
+ * These tests checks self use calls. For the most part we doesn't attempt to
+ * cover self-use, except in those cases where our clean room implementation
+ * does it.
  *
  * <p>This black box test was written without inspecting the non-free org.json
  * sourcecode.
@@ -36,6 +36,8 @@
     private int arrayGetCalls = 0;
     private int arrayOptCalls = 0;
     private int arrayOptTypeCalls = 0;
+    private int tokenerNextCalls = 0;
+    private int tokenerNextValueCalls = 0;
 
     private final JSONObject object = new JSONObject() {
         @Override public JSONObject put(String name, Object value) throws JSONException {
@@ -107,6 +109,18 @@
         }
     };
 
+    private final JSONTokener tokener = new JSONTokener("{\"foo\": [true]}") {
+        @Override public char next() {
+            tokenerNextCalls++;
+            return super.next();
+        }
+        @Override public Object nextValue() throws JSONException {
+            tokenerNextValueCalls++;
+            return super.nextValue();
+        }
+    };
+
+
     public void testObjectPut() throws JSONException {
         object.putOpt("foo", "bar");
         assertEquals(1, objectPutCalls);
@@ -201,4 +215,15 @@
         assertEquals(0, arrayOptTypeCalls);
     }
 
+    public void testNextExpecting() throws JSONException {
+        tokener.next('{');
+        assertEquals(1, tokenerNextCalls);
+        tokener.next('\"');
+        assertEquals(2, tokenerNextCalls);
+    }
+
+    public void testNextValue() throws JSONException {
+        tokener.nextValue();
+        assertEquals(4, tokenerNextValueCalls);
+    }
 }
diff --git a/logging/src/main/java/java/util/logging/LogManager.java b/logging/src/main/java/java/util/logging/LogManager.java
index 413efb3..6cba849d 100644
--- a/logging/src/main/java/java/util/logging/LogManager.java
+++ b/logging/src/main/java/java/util/logging/LogManager.java
@@ -326,12 +326,13 @@
 
         // find children
         // TODO: performance can be improved here?
+        String nameDot = name + '.';
         Collection<Logger> allLoggers = loggers.values();
         for (final Logger child : allLoggers) {
             Logger oldParent = child.getParent();
             if (parent == oldParent
                     && (name.length() == 0 || child.getName().startsWith(
-                            name + '.'))) {
+                            nameDot))) {
                 final Logger thisLogger = logger;
                 AccessController.doPrivileged(new PrivilegedAction<Object>() {
                     public Object run() {
diff --git a/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LogManagerTest.java b/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LogManagerTest.java
index 4768837..7fa258b 100644
--- a/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LogManagerTest.java
+++ b/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LogManagerTest.java
@@ -35,6 +35,7 @@
 import java.util.logging.Logger;
 import java.util.logging.LoggingPermission;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.TestCase;
 
 import org.apache.harmony.logging.tests.java.util.logging.HandlerTest.NullOutputStream;
@@ -837,6 +838,7 @@
         assertNull(m.getProperty("java.util.logging.FileHandler.pattern"));
     }
 
+    @KnownFailure("We're ignoring a missing logging.properties. See bug 2487364")
     public void testReadConfiguration() throws SecurityException,
             IOException {
 
diff --git a/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LoggerTest.java b/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LoggerTest.java
index f2bd62d..7bf74e4 100644
--- a/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LoggerTest.java
+++ b/logging/src/test/java/org/apache/harmony/logging/tests/java/util/logging/LoggerTest.java
@@ -32,6 +32,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.TestCase;
 
 import org.apache.harmony.logging.tests.java.util.logging.util.EnvironmentHelper;
@@ -4639,6 +4640,7 @@
         method = "initHandler",
         args = {}
     )
+    @KnownFailure("This test doesn't load its resources properly")
     public void test_initHandler() throws Exception {
         File logProps = new File(LOGGING_CONFIG_FILE);
         LogManager lm = LogManager.getLogManager();
diff --git a/luni-kernel/src/main/java/java/lang/System.java b/luni-kernel/src/main/java/java/lang/System.java
index aa78b1b..a238de7 100644
--- a/luni-kernel/src/main/java/java/lang/System.java
+++ b/luni-kernel/src/main/java/java/lang/System.java
@@ -89,11 +89,6 @@
     private static Properties systemProperties;
 
     /**
-     * The System default SecurityManager.
-     */
-    private static SecurityManager securityManager;
-
-    /**
      * Initialize all the slots in System on first use.
      */
     static {
@@ -496,13 +491,13 @@
     }
 
     /**
-     * Returns the active security manager.
+     * Returns null. Android does not use {@code SecurityManager}. This method
+     * is only provided for source compatibility.
      * 
-     * @return the system security manager object.
-     * @since Android 1.0
+     * @return null
      */
     public static SecurityManager getSecurityManager() {
-        return securityManager;
+        return null;
     }
 
     /**
@@ -603,13 +598,11 @@
     }
 
     /**
-     * <strong>Warning:</strong> security managers do <strong>not</strong>
-     * provide a secure environment for executing untrusted code. Untrusted code
-     * cannot be safely isolated within the Dalvik VM.
+     * Throws {@code UnsupportedOperationException}.
      *
-     * <p>Sets the active security manager. Note that once the security manager
-     * has been set, it can not be changed. Attempts to do that will cause a
-     * security exception.
+     * <p>Security managers do <i>not</i> provide a secure environment for
+     * executing untrusted code and are unsupported on Android. Untrusted code
+     * cannot be safely isolated within the Dalvik VM.
      *
      * @param sm
      *            the new security manager.
@@ -617,28 +610,11 @@
      *             if the security manager has already been set and if its
      *             checkPermission method does not allow to redefine the
      *             security manager.
-     * @since Android 1.0
      */
-    public static void setSecurityManager(final SecurityManager sm) {
-        if (securityManager != null) {
-            securityManager.checkPermission(new java.lang.RuntimePermission("setSecurityManager"));
-        }
-
+    public static void setSecurityManager(SecurityManager sm) {
         if (sm != null) {
-            // before the new manager assumed office, make a pass through
-            // the common operations and let it load needed classes (if any),
-            // to avoid infinite recursion later on
-            try {
-                sm.checkPermission(new SecurityPermission("getProperty.package.access"));
-            } catch (Exception ignore) {
-            }
-            try {
-                sm.checkPackageAccess("java.lang");
-            } catch (Exception ignore) {
-            }
+            throw new UnsupportedOperationException();
         }
-
-        securityManager = sm;
     }
 
     /**
diff --git a/luni/src/main/java/java/io/BufferedInputStream.java b/luni/src/main/java/java/io/BufferedInputStream.java
index 22379dd..6888a87 100644
--- a/luni/src/main/java/java/io/BufferedInputStream.java
+++ b/luni/src/main/java/java/io/BufferedInputStream.java
@@ -115,13 +115,13 @@
     }
 
     /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This method returns the number of bytes available in the buffer
-     * plus those available in the source stream.
+     * Returns an estimated number of bytes that can be read or skipped without blocking for more
+     * input. This method returns the number of bytes available in the buffer
+     * plus those available in the source stream, but see {@link InputStream#available} for
+     * important caveats.
      *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if this stream is closed.
+     * @return the estimated number of bytes available
+     * @throws IOException if this stream is closed or an error occurs
      */
     @Override
     public synchronized int available() throws IOException {
diff --git a/luni/src/main/java/java/io/ByteArrayInputStream.java b/luni/src/main/java/java/io/ByteArrayInputStream.java
index f0b7219..9b4b32f 100644
--- a/luni/src/main/java/java/io/ByteArrayInputStream.java
+++ b/luni/src/main/java/java/io/ByteArrayInputStream.java
@@ -85,11 +85,9 @@
     }
 
     /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This method returns the number of bytes yet to be read from the
-     * source byte array.
+     * Returns the number of remaining bytes.
      *
-     * @return the number of bytes available before blocking.
+     * @return {@code count - pos}
      */
     @Override
     public synchronized int available() {
diff --git a/luni/src/main/java/java/io/FileInputStream.java b/luni/src/main/java/java/io/FileInputStream.java
index 20f7eae..6b91544 100644
--- a/luni/src/main/java/java/io/FileInputStream.java
+++ b/luni/src/main/java/java/io/FileInputStream.java
@@ -134,15 +134,6 @@
         this(null == fileName ? (File) null : new File(fileName));
     }
 
-    /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This method always returns the size of the file minus the current
-     * position.
-     *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if an error occurs in this stream.
-     */
     @Override
     public int available() throws IOException {
         openCheck();
diff --git a/luni/src/main/java/java/io/FilterInputStream.java b/luni/src/main/java/java/io/FilterInputStream.java
index 0baa409..9891cdb 100644
--- a/luni/src/main/java/java/io/FilterInputStream.java
+++ b/luni/src/main/java/java/io/FilterInputStream.java
@@ -46,14 +46,6 @@
         this.in = in;
     }
 
-    /**
-     * Returns the number of bytes that are available before this stream will
-     * block.
-     *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if an error occurs in this stream.
-     */
     @Override
     public int available() throws IOException {
         return in.available();
diff --git a/luni/src/main/java/java/io/InputStream.java b/luni/src/main/java/java/io/InputStream.java
index 33a5cfd..b16c5a2 100644
--- a/luni/src/main/java/java/io/InputStream.java
+++ b/luni/src/main/java/java/io/InputStream.java
@@ -48,13 +48,41 @@
     }
 
     /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This implementation always returns 0. Subclasses should override
-     * and indicate the correct number of bytes available.
+     * Returns an estimated number of bytes that can be read or skipped without blocking for more
+     * input.
      *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if an error occurs in this stream.
+     * <p>Note that this method provides such a weak guarantee that it is not very useful in
+     * practice.
+     *
+     * <p>Firstly, the guarantee is "without blocking for more input" rather than "without
+     * blocking": a read may still block waiting for I/O to complete&nbsp;&mdash; the guarantee is
+     * merely that it won't have to wait indefinitely for data to be written. The result of this
+     * method should not be used as a license to do I/O on a thread that shouldn't be blocked.
+     *
+     * <p>Secondly, the result is a
+     * conservative estimate and may be significantly smaller than the actual number of bytes
+     * available. In particular, an implementation that always returns 0 would be correct.
+     * In general, callers should only use this method if they'd be satisfied with
+     * treating the result as a boolean yes or no answer to the question "is there definitely
+     * data ready?".
+     *
+     * <p>Thirdly, the fact that a given number of bytes is "available" does not guarantee that a
+     * read or skip will actually read or skip that many bytes: they may read or skip fewer.
+     *
+     * <p>It is particularly important to realize that you <i>must not</i> use this method to
+     * size a container and assume that you can read the entirety of the stream without needing
+     * to resize the container. Such callers should probably write everything they read to a
+     * {@link ByteArrayOutputStream} and convert that to a byte array. Alternatively, if you're
+     * reading from a file, {@link File#length} returns the current length of the file (though
+     * assuming the file's length can't change may be incorrect, reading a file is inherently
+     * racy).
+     *
+     * <p>The default implementation of this method in {@code InputStream} always returns 0.
+     * Subclasses should override this method if they are able to indicate the number of bytes
+     * available.
+     *
+     * @return the estimated number of bytes available
+     * @throws IOException if this stream is closed or an error occurs
      */
     public int available() throws IOException {
         return 0;
@@ -198,15 +226,16 @@
     }
 
     /**
-     * Skips at most {@code n} bytes in this stream. It does nothing and returns
-     * 0 if {@code n} is negative. Less than {@code n} characters are skipped if
-     * the end of this stream is reached before the operation completes.
-     * <p>
-     * This default implementation reads {@code n} bytes into a temporary
+     * Skips at most {@code n} bytes in this stream. This method does nothing and returns
+     * 0 if {@code n} is negative.
+     *
+     * <p>Note the "at most" in the description of this method: this method may choose to skip
+     * fewer bytes than requested. Callers should <i>always</i> check the return value.
+     *
+     * <p>This default implementation reads bytes into a temporary
      * buffer. Concrete subclasses should provide their own implementation.
      *
-     * @param n
-     *            the number of bytes to skip.
+     * @param n the number of bytes to skip.
      * @return the number of bytes actually skipped.
      * @throws IOException
      *             if this stream is closed or another IOException occurs.
diff --git a/luni/src/main/java/java/io/LineNumberInputStream.java b/luni/src/main/java/java/io/LineNumberInputStream.java
index 3df3a05..b8290cf 100644
--- a/luni/src/main/java/java/io/LineNumberInputStream.java
+++ b/luni/src/main/java/java/io/LineNumberInputStream.java
@@ -51,17 +51,12 @@
     }
 
     /**
-     * Returns the number of bytes that are available before this stream will
-     * block.
-     * <p>
-     * Note: The source stream may just be a sequence of {@code "\r\n"} bytes
+     * {@inheritDoc}
+     *
+     * <p>Note that the source stream may just be a sequence of {@code "\r\n"} bytes
      * which are converted into {@code '\n'} by this stream. Therefore,
      * {@code available} returns only {@code in.available() / 2} bytes as
      * result.
-     *
-     * @return the guaranteed number of bytes available before blocking.
-     * @throws IOException
-     *             if an error occurs in this stream.
      */
     @Override
     public int available() throws IOException {
diff --git a/luni/src/main/java/java/io/ObjectInputStream.java b/luni/src/main/java/java/io/ObjectInputStream.java
index df6d9a2..9d79641a 100644
--- a/luni/src/main/java/java/io/ObjectInputStream.java
+++ b/luni/src/main/java/java/io/ObjectInputStream.java
@@ -446,16 +446,6 @@
         primitiveData = emptyStream;
     }
 
-    /**
-     * Returns the number of bytes of primitive data that can be read from this
-     * stream without blocking. This method should not be used at any arbitrary
-     * position; just when reading primitive data types (int, char etc).
-     *
-     * @return the number of available primitive data bytes.
-     * @throws IOException
-     *             if any I/O problem occurs while computing the available
-     *             bytes.
-     */
     @Override
     public int available() throws IOException {
         // returns 0 if next data is an object, or N if reading primitive types
diff --git a/luni/src/main/java/java/io/PipedInputStream.java b/luni/src/main/java/java/io/PipedInputStream.java
index 6d1c007..025d6b1 100644
--- a/luni/src/main/java/java/io/PipedInputStream.java
+++ b/luni/src/main/java/java/io/PipedInputStream.java
@@ -103,13 +103,13 @@
     }
 
     /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This implementation returns the number of bytes written to this
-     * pipe that have not been read yet.
+     * {@inheritDoc}
      *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if an error occurs in this stream.
+     * <p>Unlike most streams, {@code PipedInputStream} returns 0 rather than throwing
+     * {@code IOException} if the stream has been closed. Unconnected and broken pipes also
+     * return 0.
+     *
+     * @throws IOException if an I/O error occurs
      */
     @Override
     public synchronized int available() throws IOException {
diff --git a/luni/src/main/java/java/io/PushbackInputStream.java b/luni/src/main/java/java/io/PushbackInputStream.java
index 932a896..5a78eb9 100644
--- a/luni/src/main/java/java/io/PushbackInputStream.java
+++ b/luni/src/main/java/java/io/PushbackInputStream.java
@@ -73,16 +73,6 @@
         pos = size;
     }
 
-    /**
-     * Returns the number of bytes that are available before this stream will
-     * block. This is the sum of the bytes available in the pushback buffer and
-     * those available from the source stream.
-     *
-     * @return the number of bytes available before blocking.
-     * @throws IOException
-     *             if this stream is closed or an I/O error occurs in the source
-     *             stream.
-     */
     @Override
     public int available() throws IOException {
         if (buf == null) {
diff --git a/luni/src/main/java/java/io/SequenceInputStream.java b/luni/src/main/java/java/io/SequenceInputStream.java
index 3719021..c72e521 100644
--- a/luni/src/main/java/java/io/SequenceInputStream.java
+++ b/luni/src/main/java/java/io/SequenceInputStream.java
@@ -81,14 +81,6 @@
         }
     }
 
-    /**
-     * Returns the number of bytes that are available before the current input stream will
-     * block.
-     *
-     * @return the number of bytes available in the current input stream before blocking.
-     * @throws IOException
-     *             if an I/O error occurs in the current input stream.
-     */
     @Override
     public int available() throws IOException {
         if (e != null && in != null) {
diff --git a/luni/src/main/java/java/io/StringBufferInputStream.java b/luni/src/main/java/java/io/StringBufferInputStream.java
index 037cc60..32e89d7 100644
--- a/luni/src/main/java/java/io/StringBufferInputStream.java
+++ b/luni/src/main/java/java/io/StringBufferInputStream.java
@@ -60,12 +60,6 @@
         count = str.length();
     }
 
-    /**
-     * Returns the number of bytes that are available before this stream will
-     * block.
-     *
-     * @return the number of bytes available before blocking.
-     */
     @Override
     public synchronized int available() {
         return count - pos;
diff --git a/luni/src/main/java/java/net/InetAddress.java b/luni/src/main/java/java/net/InetAddress.java
index ad96ed6..41d49b2 100644
--- a/luni/src/main/java/java/net/InetAddress.java
+++ b/luni/src/main/java/java/net/InetAddress.java
@@ -410,11 +410,39 @@
     }
 
     /**
-     * Gets the local host address if the security policy allows this.
-     * Otherwise, gets the loopback address which allows this machine to be
-     * contacted.
+     * Returns an {@code InetAddress} for the local host if possible, or the
+     * loopback address otherwise. This method works by getting the hostname,
+     * performing a DNS lookup, and then taking the first returned address.
+     * For devices with multiple network interfaces and/or multiple addresses
+     * per interface, this does not necessarily return the {@code InetAddress}
+     * you want.
      *
-     * @return the {@code InetAddress} representing the local host.
+     * <p>Multiple interface/address configurations were relatively rare
+     * when this API was designed, but multiple interfaces are the default for
+     * modern mobile devices (with separate wifi and radio interfaces), and
+     * the need to support both IPv4 and IPv6 has made multiple addresses
+     * commonplace. New code should thus avoid this method except where it's
+     * basically being used to get a loopback address or equivalent.
+     *
+     * <p>There are two main ways to get a more specific answer:
+     * <ul>
+     * <li>If you have a connected socket, you should probably use
+     * {@link Socket#getLocalAddress} instead: that will give you the address
+     * that's actually in use for that connection. (It's not possible to ask
+     * the question "what local address would a connection to a given remote
+     * address use?"; you have to actually make the connection and see.)</li>
+     * <li>For other use cases, see {@link NetworkInterface}, which lets you
+     * enumerate all available network interfaces and their addresses.</li>
+     * </ul>
+     *
+     * <p>Note that if the host doesn't have a hostname set&nbsp;&ndash; as
+     * Android devices typically don't&nbsp;&ndash; this method will
+     * effectively return the loopback address, albeit by getting the name
+     * {@code localhost} and then doing a lookup to translate that to
+     * {@code 127.0.0.1}.
+     *
+     * @return an {@code InetAddress} representing the local host, or the
+     * loopback address.
      * @throws UnknownHostException
      *             if the address lookup fails.
      */
diff --git a/luni/src/main/java/java/util/Random.java b/luni/src/main/java/java/util/Random.java
index f893020..d6fe0f6 100644
--- a/luni/src/main/java/java/util/Random.java
+++ b/luni/src/main/java/java/util/Random.java
@@ -52,7 +52,6 @@
      */
     private double nextNextGaussian;
 
-    // BEGIN android-changed
     /**
      * Constructs a random generator with an initial state that is
      * unlikely to be duplicated by a subsequent instantiation.
@@ -64,14 +63,12 @@
      */
     public Random() {
         // Note: Using identityHashCode() to be hermetic wrt subclasses.
-        internalSetSeed(
-                System.currentTimeMillis() + System.identityHashCode(this));
+        setSeed(System.currentTimeMillis() + System.identityHashCode(this));
     }
-    // END android-changed
 
     /**
      * Construct a random generator with the given {@code seed} as the
-     * initial state.
+     * initial state. Equivalent to {@code Random r = new Random(); r.setSeed(seed);}.
      * 
      * @param seed
      *            the seed that will determine the initial state of this random
@@ -79,9 +76,7 @@
      * @see #setSeed
      */
     public Random(long seed) {
-        // BEGIN android-changed
-        internalSetSeed(seed);
-        // END android-changed
+        setSeed(seed);
     }
 
     /**
@@ -245,7 +240,6 @@
         return ((long) next(32) << 32) + next(32);
     }
 
-    // BEGIN android-changed
     /**
      * Modifies the seed a using linear congruential formula presented in <i>The
      * Art of Computer Programming, Volume 2</i>, Section 3.2.1.
@@ -257,20 +251,7 @@
      * @see #Random(long)
      */
     public synchronized void setSeed(long seed) {
-        internalSetSeed(seed);
-    }
-
-    /**
-     * Sets the seed. This is used both in the constructor and in the
-     * default implementation of {@link #setSeed}.
-     *
-     * @param seed
-     *            the seed that alters the state of the random number
-     *            generator.
-     */
-    private void internalSetSeed(long seed) {
         this.seed = (seed ^ multiplier) & ((1L << 48) - 1);
         haveNextNextGaussian = false;
     }
-    // END android-changed
 }
diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
index 12c3dd9..4680ca6 100644
--- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
+++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp
@@ -493,7 +493,7 @@
 
     // Accept IPv6 addresses (only) in square brackets for compatibility.
     if (ipString[0] == '[' && ipString[byteCount - 1] == ']' &&
-            index(ipString, ':') != NULL) {
+            strchr(ipString, ':') != NULL) {
         memmove(ipString, ipString + 1, byteCount - 2);
         ipString[byteCount - 2] = '\0';
     }
diff --git a/luni/src/test/java/java/util/AllTests.java b/luni/src/test/java/java/util/AllTests.java
index 774d48b..6af29d8 100644
--- a/luni/src/test/java/java/util/AllTests.java
+++ b/luni/src/test/java/java/util/AllTests.java
@@ -25,6 +25,7 @@
         suite.addTestSuite(java.util.CurrencyTest.class);
         suite.addTestSuite(java.util.DateTest.class);
         suite.addTestSuite(java.util.FormatterTest.class);
+        suite.addTestSuite(java.util.RandomTest.class);
         suite.addTestSuite(java.util.TimeZoneTest.class);
         return suite;
     }
diff --git a/luni/src/test/java/java/util/RandomTest.java b/luni/src/test/java/java/util/RandomTest.java
new file mode 100644
index 0000000..e6473cb
--- /dev/null
+++ b/luni/src/test/java/java/util/RandomTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class RandomTest extends junit.framework.TestCase {
+    public void test_subclassing() throws Exception {
+        // http://b/2502231
+        // Ensure that Random's constructors call setSeed by emulating the active ingredient
+        // from the bug: the subclass' setSeed had a side-effect necessary for the correct
+        // functioning of next.
+        class MyRandom extends Random {
+            public String state;
+            public MyRandom() { super(); }
+            public MyRandom(long l) { super(l); }
+            @Override protected synchronized int next(int bits) { return state.length(); }
+            @Override public synchronized void setSeed(long seed) { state = Long.toString(seed); }
+        }
+        // Test the 0-argument constructor...
+        MyRandom r1 = new MyRandom();
+        r1.nextInt();
+        assertNotNull(r1.state);
+        // Test the 1-argument constructor...
+        MyRandom r2 = new MyRandom(123L);
+        r2.nextInt();
+        assertNotNull(r2.state);
+    }
+}
diff --git a/luni/src/test/java/tests/api/java/io/PipedInputStreamTest.java b/luni/src/test/java/tests/api/java/io/PipedInputStreamTest.java
index 671bfe9..c6dd60f 100644
--- a/luni/src/test/java/tests/api/java/io/PipedInputStreamTest.java
+++ b/luni/src/test/java/tests/api/java/io/PipedInputStreamTest.java
@@ -27,6 +27,8 @@
 @TestTargetClass(PipedInputStream.class) 
 public class PipedInputStreamTest extends junit.framework.TestCase {
 
+    private final int BUFFER_SIZE = 1024;
+
     static class PWriter implements Runnable {
         PipedOutputStream pos;
 
@@ -150,13 +152,12 @@
 
         PipedInputStream pin = new PipedInputStream();
         PipedOutputStream pout = new PipedOutputStream(pin);
-        // We know the PipedInputStream buffer size is 1024.
         // Writing another byte would cause the write to wait
         // for a read before returning
-        for (int i = 0; i < 1024; i++)
+        for (int i = 0; i < BUFFER_SIZE; i++)
             pout.write(i);
         assertEquals("Test 2: Incorrect number of bytes available. ", 
-                     1024 , pin.available());
+                     BUFFER_SIZE, pin.available());
     }
 
     /**
@@ -433,8 +434,9 @@
                         Thread.sleep(100);
                     }
                     try {
-                        // should throw exception since reader thread
-                        // is now dead
+                        pos.write(new byte[BUFFER_SIZE]);
+                        // should throw exception since buffer is full and
+                        // reader thread is now dead
                         pos.write(1);
                     } catch (IOException e) {
                         pass = true;
diff --git a/nio/src/test/java/org/apache/harmony/nio/tests/java/nio/channels/ServerSocketChannelTest.java b/nio/src/test/java/org/apache/harmony/nio/tests/java/nio/channels/ServerSocketChannelTest.java
index b9b880d..7eb03b3 100644
--- a/nio/src/test/java/org/apache/harmony/nio/tests/java/nio/channels/ServerSocketChannelTest.java
+++ b/nio/src/test/java/org/apache/harmony/nio/tests/java/nio/channels/ServerSocketChannelTest.java
@@ -21,8 +21,8 @@
 import dalvik.annotation.TestTargets;
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.AndroidOnly;
 import dalvik.annotation.BrokenTest;
+import dalvik.annotation.KnownFailure;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -724,7 +724,7 @@
         method = "accept",
         args = {}
     )
-    @AndroidOnly("seems to run on newer RI versions")
+    @KnownFailure("http://b/1952042 - issues with sockets forgetting their local and remote addresses")
     public void test_accept_Security() throws IOException {
         this.clientChannel.configureBlocking(true);
         this.serverChannel.configureBlocking(true);
diff --git a/security/src/main/files/certimport.sh b/security/src/main/files/certimport.sh
index ca36a70..f7bd8c8a 100755
--- a/security/src/main/files/certimport.sh
+++ b/security/src/main/files/certimport.sh
@@ -67,3 +67,12 @@
       -storepass $STOREPASS
   let "COUNTER=$COUNTER + 1"
 done
+
+keytool \
+      -list \
+      -v \
+      -keystore $CERTSTORE \
+      -storetype BKS \
+      -provider $PROVIDER_CLASS \
+      -providerpath $PROVIDER_PATH \
+      -storepass $STOREPASS
diff --git a/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
index f0e47d9..cc175df 100644
--- a/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
+++ b/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
@@ -800,26 +800,17 @@
             this.inStream = inStream;
         }
 
-        /**
-         * @see java.io.InputStream#available()
-         * method documentation for more info
-         */
+        @Override
         public int available() throws IOException {
             return (bar - pos) + inStream.available();
         }
 
-        /**
-         * @see java.io.InputStream#close()
-         * method documentation for more info
-         */
+        @Override
         public void close() throws IOException {
             inStream.close();
         }
 
-        /**
-         * @see java.io.InputStream#mark(int readlimit)
-         * method documentation for more info
-         */
+        @Override
         public void mark(int readlimit) {
             if (pos < 0) {
                 pos = 0;
@@ -830,10 +821,7 @@
             }
         }
 
-        /**
-         * @see java.io.InputStream#markSupported()
-         * method documentation for more info
-         */
+        @Override
         public boolean markSupported() {
             return true;
         }
@@ -881,18 +869,12 @@
             return inStream.read();
         }
 
-        /**
-         * @see java.io.InputStream#read(byte[] b)
-         * method documentation for more info
-         */
+        @Override
         public int read(byte[] b) throws IOException {
             return read(b, 0, b.length);
         }
 
-        /**
-         * @see java.io.InputStream#read(byte[] b, int off, int len)
-         * method documentation for more info
-         */
+        @Override
         public int read(byte[] b, int off, int len) throws IOException {
             int read_b;
             int i;
@@ -905,10 +887,7 @@
             return i;
         }
 
-        /**
-         * @see java.io.InputStream#reset()
-         * method documentation for more info
-         */
+        @Override
         public void reset() throws IOException {
             if (pos >= 0) {
                 pos = (end + 1) % BUFF_SIZE;
@@ -918,10 +897,7 @@
             }
         }
 
-        /**
-         * @see java.io.InputStream#skip(long n)
-         * method documentation for more info
-         */
+        @Override
         public long skip(long n) throws IOException {
             if (pos >= 0) {
                 long i = 0;
@@ -939,6 +915,3 @@
         }
     }
 }
-
-
-
diff --git a/security/src/test/java/tests/security/permissions/JavaIoFileTest.java b/security/src/test/java/tests/security/permissions/JavaIoFileTest.java
index 7bc6933..207bfc0 100644
--- a/security/src/test/java/tests/security/permissions/JavaIoFileTest.java
+++ b/security/src/test/java/tests/security/permissions/JavaIoFileTest.java
@@ -22,6 +22,7 @@
 import java.io.IOException;
 import java.security.Permission;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.TestCase;
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
@@ -431,6 +432,7 @@
             args = {}
         )
     })
+    @KnownFailure("We need to finish cleaning up java.io.File (bug 2281992)")
     public void test_File4() throws IOException {
         class TestSecurityManager extends SecurityManager {
             boolean checkPropertyAccessCalled;
@@ -487,6 +489,7 @@
             args = {}
         )
     })
+    @KnownFailure("We need to finish cleaning up java.io.File (bug 2281992)")
     public void test_File5() throws IOException {
         class TestSecurityManager extends SecurityManager {
             boolean checkPropertyAccessCalled;
diff --git a/sql/src/main/java/SQLite/Blob.java b/sql/src/main/java/SQLite/Blob.java
index 3de9f8a..16fecf1 100644
--- a/sql/src/main/java/SQLite/Blob.java
+++ b/sql/src/main/java/SQLite/Blob.java
@@ -30,11 +30,7 @@
     this.pos = 0;
     }
 
-    /**
-     * Return number of available bytes for reading.
-     * @return available input bytes
-     */
-
+    @Override
     public int available() throws IOException {
     int ret = blob.size - pos;
     return (ret < 0) ? 0 : ret;
diff --git a/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java b/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
index d53f078..6fa7949 100644
--- a/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
+++ b/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
@@ -372,6 +372,7 @@
         method = "getDrivers",
         args = {}
     )
+    @KnownFailure("We're working out issues with built-in SQL drivers")
     public void testGetDrivers() {
         // Load a driver manager
         Enumeration<Driver> driverList = DriverManager.getDrivers();
@@ -716,6 +717,7 @@
     /**
      * Regression for HARMONY-4303
      */
+    @KnownFailure("The test doesn't fork the VM properly.")
     public void test_initClass() throws Exception {
         ProcessBuilder builder = javaProcessBuilder();
         builder.command().add("org/apache/harmony/sql/tests/java/sql/TestMainForDriver");
diff --git a/support/src/test/java/tests/util/TestEnvironment.java b/support/src/test/java/tests/util/TestEnvironment.java
index 088e624..fd02123 100644
--- a/support/src/test/java/tests/util/TestEnvironment.java
+++ b/support/src/test/java/tests/util/TestEnvironment.java
@@ -145,7 +145,12 @@
     }
 
     private static void copyProperty(Properties p, String key) {
-        p.put(key, System.getProperty(key));
+        String value = System.getProperty(key);
+        if (value != null) {
+            p.put(key, value);
+        } else {
+            p.remove(key);
+        }
     }
 
     private static void makeDirectory(File path) {
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
index a150470..31bb681 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
@@ -49,9 +49,6 @@
         this.in = in;
     }
 
-    /**
-     * Returns the number of bytes available for reading.
-     */
     @Override
     public int available() throws IOException {
         // in assumption that the buffer has been set
@@ -78,4 +75,3 @@
         return bytik;
     }
 }
-
diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
index 507e14f..b2501a7 100644
--- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
+++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
@@ -29,9 +29,6 @@
  */
 public abstract class SSLInputStream extends InputStream {
 
-    /**
-     * @see java.io.InputStream#available()
-     */
     @Override
     public abstract int available() throws IOException;
 
@@ -48,9 +45,6 @@
     @Override
     public abstract int read() throws IOException;
 
-    /**
-     * @see java.io.InputStream#skip(long)
-     */
     @Override
     public long skip(long n) throws IOException {
         long skept = n;
@@ -115,9 +109,6 @@
         return res;
     }
 
-    /**
-     * @see java.io.InputStream#read(byte[],int,int)
-     */
     @Override
     public int read(byte[] b, int off, int len) throws IOException {
         int read_b;
diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index 407ca0d..d0682a4 100644
--- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -503,7 +503,6 @@
                     line,
                     data,
                     flags);
-            LOGD("XXX bdc str %s", str);
 
             if (ret < 0) {
                 break;
@@ -736,10 +735,12 @@
     data->fdsEmergency[1] = -1;
 
     if (pipe(data->fdsEmergency) == -1) {
+        free(data);
         return -1;
     }
 
     if (MUTEX_SETUP(data->mutex) == -1) {
+        free(data);
         return -1;
     }
 
@@ -1716,9 +1717,16 @@
 }
 
 static jobjectArray makeCipherList(JNIEnv* env, SSL* ssl) {
+    STACK_OF(SSL_CIPHER)* cipher_list = SSL_get_ciphers(ssl);
     // Count the ciphers.
+    int num = sk_SSL_CIPHER_num(cipher_list);
     int cipherCount = 0;
-    while (SSL_get_cipher_list(ssl, cipherCount) != NULL) {
+    for (int i = 0; i < num; ++i) {
+        SSL_CIPHER* cipher = sk_SSL_CIPHER_value(cipher_list, i);
+        if (strcmp(SSL_CIPHER_get_version(cipher), SSL_TXT_SSLV2) == 0) {
+            // openssl-1.0.0 includes duplicate names for SSLv2 and SSLv3 ciphers
+            continue;
+        }
         ++cipherCount;
     }
 
@@ -1733,9 +1741,14 @@
     }
 
     // Fill in the cipher names.
-    for (int i = 0; i < cipherCount; ++i) {
-        const char* c = SSL_get_cipher_list(ssl, i);
-        env->SetObjectArrayElement(array, i, env->NewStringUTF(c));
+    int cipherIndex = 0;
+    for (int i = 0; i < num; ++i) {
+        SSL_CIPHER* cipher = sk_SSL_CIPHER_value(cipher_list, i);
+        if (strcmp(SSL_CIPHER_get_version(cipher), SSL_TXT_SSLV2) == 0) {
+            continue;
+        }
+        env->SetObjectArrayElement(array, cipherIndex, env->NewStringUTF(cipher->name));
+        ++cipherIndex;
     }
     return array;
 }
@@ -1800,15 +1813,14 @@
     setEnabledCipherSuites(env, controlString, ssl_ctx);
 }
 
-#define SSL_AUTH_MASK           0x00007F00L
-#define SSL_aRSA                0x00000100L /* Authenticate with RSA */
-#define SSL_aDSS                0x00000200L /* Authenticate with DSS */
-#define SSL_DSS                 SSL_aDSS
-#define SSL_aFZA                0x00000400L
-#define SSL_aNULL               0x00000800L /* no Authenticate, ADH */
-#define SSL_aDH                 0x00001000L /* no Authenticate, ADH */
-#define SSL_aKRB5               0x00002000L /* Authenticate with KRB5 */
-#define SSL_aECDSA              0x00004000L /* Authenticate with ECDSA */
+#define SSL_aRSA                0x00000001L
+#define SSL_aDSS                0x00000002L
+#define SSL_aNULL               0x00000004L
+#define SSL_aDH                 0x00000008L
+#define SSL_aECDH               0x00000010L
+#define SSL_aKRB5               0x00000020L
+#define SSL_aECDSA              0x00000040L
+#define SSL_aPSK                0x00000080L
 
 /**
  * Sets  the client's crypto algorithms and authentication methods.
@@ -1817,10 +1829,10 @@
         jobject object)
 {
     SSL* ssl;
-    SSL_CIPHER *cipher;
+    const SSL_CIPHER *cipher;
     jstring ret;
     char buf[512];
-    unsigned long alg;
+    unsigned long alg_auth;
     const char *au;
 
     ssl = getSslPointer(env, object, true);
@@ -1830,9 +1842,9 @@
 
     cipher = SSL_get_current_cipher(ssl);
 
-    alg = cipher->algorithms;
+    alg_auth = cipher->algorithm_auth;
 
-    switch (alg&SSL_AUTH_MASK) {
+    switch (alg_auth) {
         case SSL_aRSA:
             au="RSA";
             break;
@@ -1842,8 +1854,11 @@
         case SSL_aDH:
             au="DH";
             break;
-        case SSL_aFZA:
-            au = "FZA";
+        case SSL_aKRB5:
+            au="KRB5";
+            break;
+        case SSL_aECDH:
+            au = "ECDH";
             break;
         case SSL_aNULL:
             au="None";
@@ -1851,6 +1866,9 @@
         case SSL_aECDSA:
             au="ECDSA";
             break;
+        case SSL_aPSK:
+            au="PSK";
+            break;
         default:
             au="unknown";
             break;
@@ -2051,7 +2069,8 @@
              * let the Java layer know about this by throwing an
              * exception.
              */ 
-            throwIOExceptionWithSslErrors(env, ret, 0, "SSL shutdown failed.");
+            int sslErrorCode = SSL_get_error(ssl, ret);
+            throwIOExceptionWithSslErrors(env, ret, sslErrorCode, "SSL shutdown failed");
             break;
     }
 
@@ -2512,7 +2531,7 @@
 
     SSL_set_session(ssl, ssl_session);
 
-    SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
+    const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
     jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher));
 
     SSL_free(ssl);
diff --git a/x-net/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java b/x-net/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
index fa36d0c..aebde6b 100644
--- a/x-net/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
+++ b/x-net/src/test/java/tests/api/javax/net/ssl/HandshakeCompletedEventTest.java
@@ -500,8 +500,8 @@
     
     /** 
      * Implements a test SSL socket server. It wait for a connection on a given
-     * port, requests client authentication (if specified), and read 256 bytes
-     * from the socket. 
+     * port, requests client authentication (if specified), reads 256 bytes
+     * from the socket, and writes 256 bytes to the socket.
      */
     class TestServer implements Runnable {
 
@@ -551,16 +551,26 @@
                 
                 SSLSocket clientSocket = (SSLSocket)serverSocket.accept();
 
-                InputStream stream = clientSocket.getInputStream();
+                InputStream istream = clientSocket.getInputStream();
 
                 for (int i = 0; i < 256; i++) {
-                    int j = stream.read();
+                    int j = istream.read();
                     if (i != j) {
                         throw new RuntimeException("Error reading socket, expected " + i + ", got " + j);
                     }
                 }
                 
-                stream.close();
+                istream.close();
+
+                OutputStream ostream = clientSocket.getOutputStream();
+
+                for (int i = 0; i < 256; i++) {
+                    ostream.write(i);
+                }
+
+                ostream.flush();
+                ostream.close();
+
                 clientSocket.close();
                 serverSocket.close();
                 
@@ -581,7 +591,8 @@
 
     /** 
      * Implements a test SSL socket client. It open a connection to localhost on
-     * a given port and writes 256 bytes to the socket. 
+     * a given port, writes 256 bytes to the socket, and reads 256 bytes from the
+     * socket.
      */
     class TestClient implements Runnable {
         
@@ -614,14 +625,26 @@
                 socket.addHandshakeCompletedListener(listener);
                 socket.startHandshake();
 
-                OutputStream stream = socket.getOutputStream();
+                OutputStream ostream = socket.getOutputStream();
                 
                 for (int i = 0; i < 256; i++) {
-                    stream.write(i);
+                    ostream.write(i);
                 }
                 
-                stream.flush();
-                stream.close();
+                ostream.flush();
+                ostream.close();
+
+                InputStream istream = socket.getInputStream();
+
+                for (int i = 0; i < 256; i++) {
+                    int j = istream.read();
+                    if (i != j) {
+                        throw new RuntimeException("Error reading socket, expected " + i + ", got " + j);
+                    }
+                }
+
+                istream.close();
+
                 socket.close();
                 
             } catch (Exception ex) {
@@ -649,7 +672,7 @@
         KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
         keyStore.load(inputStream, PASSWORD.toCharArray());
         inputStream.close();
-        
+
         String algorithm = KeyManagerFactory.getDefaultAlgorithm();
         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm);
         keyManagerFactory.init(keyStore, PASSWORD.toCharArray());
diff --git a/x-net/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java b/x-net/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
index c4bae0a..d12959b 100644
--- a/x-net/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
+++ b/x-net/src/test/java/tests/api/javax/net/ssl/SSLServerSocketTest.java
@@ -33,6 +33,7 @@
 import java.net.InetAddress;
 import java.security.KeyStore;
 import java.security.SecureRandom;
+import java.util.Arrays;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
@@ -327,7 +328,9 @@
         sss.setEnabledCipherSuites(sss.getSupportedCipherSuites());
         String[] res = sss.getEnabledCipherSuites();
         assertNotNull("NULL result", res);
-        assertTrue("No enabled cipher suites.", res.length == count);
+        assertEquals("not all supported cipher suites were enabled",
+                     Arrays.asList(sss.getSupportedCipherSuites()),
+                     Arrays.asList(res));
     }
     
     /**
diff --git a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
index 430d117..384084f 100644
--- a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
+++ b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java
@@ -706,8 +706,8 @@
 
     /** 
      * Implements a test SSL socket server. It waits for a connection on a given
-     * port, requests client authentication (if specified), and reads
-     * from the socket. 
+     * port, requests client authentication (if specified), reads from the socket,
+     * and writes to the socket.
      */
     class TestServer implements Runnable {
 
@@ -761,8 +761,16 @@
                 
                 SSLSocket clientSocket = (SSLSocket)serverSocket.accept();
 
+                InputStream istream = clientSocket.getInputStream();
+                byte[] buffer = new byte[1024];
+                istream.read(buffer);
+
+                OutputStream ostream = clientSocket.getOutputStream();
+                ostream.write(testData.getBytes());
+                ostream.flush();
+
                 while (notFinished) {
-                    clientSocket.getInputStream().read();
+                    Thread.currentThread().sleep(500);
                 }
 
                 clientSocket.close();
@@ -788,8 +796,8 @@
     }
 
     /** 
-     * Implements a test SSL socket client. It open a connection to localhost on
-     * a given port and writes to the socket. 
+     * Implements a test SSL socket client. It opens a connection to localhost on
+     * a given port, writes to the socket, and reads from the socket.
      */
     class TestClient implements Runnable {
         
@@ -826,6 +834,10 @@
                 ostream.write(testData.getBytes());
                 ostream.flush();
 
+                InputStream istream = socket.getInputStream();
+                byte[] buffer = new byte[1024];
+                istream.read(buffer);
+
                 clientSession = socket.getSession();
                 while (notFinished) {
                     Thread.currentThread().sleep(500);
diff --git a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
index 13a0e59..a17df93 100644
--- a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
+++ b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSocketTest.java
@@ -27,7 +27,7 @@
 import java.net.*;
 import java.security.KeyStore;
 import java.security.SecureRandom;
-import java.lang.String;
+import java.util.Arrays;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -636,8 +636,10 @@
         }
         ssl.setEnabledCipherSuites(ssl.getSupportedCipherSuites());
         String[] res = ssl.getEnabledCipherSuites();
-        assertEquals("not all supported cipher suites where enabled",
-                ssl.getSupportedCipherSuites().length, res.length);
+        assertNotNull("NULL result", res);
+        assertEquals("not all supported cipher suites were enabled",
+                     Arrays.asList(ssl.getSupportedCipherSuites()), 
+                     Arrays.asList(res));
     }
     
     /**
diff --git a/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java b/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
index dbe3a3a..d187456 100644
--- a/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
+++ b/xml/src/main/java/org/apache/harmony/xml/ExpatReader.java
@@ -244,7 +244,7 @@
     }
 
     public void parse(InputSource input) throws IOException, SAXException {
-        if (processNamespacePrefixes == processNamespaces) {
+        if (processNamespacePrefixes && processNamespaces) {
             /*
              * Expat has XML_SetReturnNSTriplet, but that still doesn't
              * include xmlns attributes like this feature requires. We may
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
index e995174..c601de6 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/AttrImpl.java
@@ -36,47 +36,19 @@
 
     // Maintained by ElementImpl.
     ElementImpl ownerElement;
+    boolean isId;
 
-    private boolean namespaceAware;
-    
-    private String namespaceURI;
+    boolean namespaceAware;
+    String namespaceURI;
+    String prefix;
+    String localName;
 
-    private String localName;
-
-    private String prefix;
-    
     private String value;
 
     AttrImpl(DocumentImpl document, String namespaceURI, String qualifiedName) {
         super(document);
 
-        namespaceAware = true;
-        this.namespaceURI = namespaceURI;
-
-        if (qualifiedName == null || "".equals(qualifiedName)) {
-            throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
-        }
-        
-        int prefixSeparator = qualifiedName.lastIndexOf(":");
-        if (prefixSeparator != -1) {
-            setPrefix(qualifiedName.substring(0, prefixSeparator));
-            qualifiedName = qualifiedName.substring(prefixSeparator + 1);
-        }
-
-        localName = qualifiedName;
-        
-        if ("".equals(localName)) {
-            throw new DOMException(DOMException.NAMESPACE_ERR, localName);
-        }
-        
-        if ("xmlns".equals(localName) && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) {
-            throw new DOMException(DOMException.NAMESPACE_ERR, localName);
-        }
-            
-        if (!DocumentImpl.isXMLIdentifier(localName)) {
-            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, localName);
-        }
-            
+        setNameNS(this, namespaceURI, qualifiedName);
         value = "";
     }
 
@@ -159,10 +131,11 @@
     }
 
     public TypeInfo getSchemaTypeInfo() {
-        throw new UnsupportedOperationException(); // TODO
+        // TODO: populate this when we support XML Schema
+        return NULL_TYPE_INFO;
     }
 
     public boolean isId() {
-        throw new UnsupportedOperationException(); // TODO
+        return isId;
     }
 }
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
index b662a13..3106c3f 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DOMImplementationImpl.java
@@ -85,6 +85,6 @@
     }
 
     public Object getFeature(String feature, String version) {
-        throw new UnsupportedOperationException(); // TODO
+        return hasFeature(feature, version) ? this : null;
     }
 }
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
index c677e58..56283a8 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/DocumentImpl.java
@@ -299,6 +299,16 @@
         }
     }
 
+    public Node renameNode(Node node, String namespaceURI, String qualifiedName) {
+        if (node.getOwnerDocument() != this) {
+            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, null);
+        }
+
+        setNameNS((NodeImpl) node, namespaceURI, qualifiedName);
+        notifyUserDataHandlers(UserDataHandler.NODE_RENAMED, node, null);
+        return node;
+    }
+
     public AttrImpl createAttribute(String name) {
         return new AttrImpl(this, name);
     }
@@ -467,11 +477,6 @@
         ((DOMConfigurationImpl) getDomConfig()).normalize(root);
     }
 
-    public Node renameNode(Node n, String namespaceURI, String qualifiedName) {
-        // TODO: callback the UserDataHandler with a NODE_RENAMED event
-        throw new UnsupportedOperationException(); // TODO
-    }
-
     /**
      * Returns a map with the user data objects attached to the specified node.
      * This map is readable and writable.
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
index e272a3e..cbc4570 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/ElementImpl.java
@@ -39,37 +39,16 @@
  */
 public class ElementImpl extends InnerNodeImpl implements Element {
 
-    private boolean namespaceAware;
-    
-    private String namespaceURI;
-
-    private String prefix;
-    
-    private String localName;
+    boolean namespaceAware;
+    String namespaceURI;
+    String prefix;
+    String localName;
 
     private List<AttrImpl> attributes = new ArrayList<AttrImpl>();
 
     ElementImpl(DocumentImpl document, String namespaceURI, String qualifiedName) {
         super(document);
-
-        this.namespaceAware = true;
-        this.namespaceURI = namespaceURI;
-
-        if (qualifiedName == null || "".equals(qualifiedName)) {
-            throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
-        }
-        
-        int p = qualifiedName.lastIndexOf(":");
-        if (p != -1) {
-            setPrefix(qualifiedName.substring(0, p));
-            qualifiedName = qualifiedName.substring(p + 1);
-        }
-        
-        if (!DocumentImpl.isXMLIdentifier(qualifiedName)) {
-            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, qualifiedName);
-        }
-            
-        this.localName = qualifiedName;
+        setNameNS(this, namespaceURI, qualifiedName);
     }
 
     ElementImpl(DocumentImpl document, String name) {
@@ -136,7 +115,7 @@
         return attr.getValue();
     }
 
-    public Attr getAttributeNode(String name) {
+    public AttrImpl getAttributeNode(String name) {
         int i = indexOfAttribute(name);
         
         if (i == -1) {
@@ -146,7 +125,7 @@
         return attributes.get(i);
     }
 
-    public Attr getAttributeNodeNS(String namespaceURI, String localName) {
+    public AttrImpl getAttributeNodeNS(String namespaceURI, String localName) {
         int i = indexOfAttributeNS(namespaceURI, localName);
         
         if (i == -1) {
@@ -160,8 +139,25 @@
     public NamedNodeMap getAttributes() {
         return new ElementAttrNamedNodeMapImpl();
     }
-    
+
+    /**
+     * This implementation walks the entire document looking for an element
+     * with the given ID attribute. We should consider adding an index to speed
+     * navigation of large documents.
+     */
     Element getElementById(String name) {
+        for (Attr attr : attributes) {
+            if (attr.isId() && name.equals(attr.getValue())) {
+                return this;
+            }
+        }
+
+        /*
+         * TODO: Remove this behavior.
+         * The spec explicitly says that this is a bad idea. From
+         * Document.getElementById(): "Attributes with the name "ID"
+         * or "id" are not of type ID unless so defined.
+         */
         if (name.equals(getAttribute("id"))) {
             return this;
         }
@@ -366,7 +362,7 @@
     public void setPrefix(String prefix) {
         this.prefix = validatePrefix(prefix, namespaceAware, namespaceURI);
     }
-    
+
     public class ElementAttrNamedNodeMapImpl implements NamedNodeMap {
 
         public int getLength() {
@@ -432,20 +428,30 @@
     }
 
     public TypeInfo getSchemaTypeInfo() {
-        throw new UnsupportedOperationException(); // TODO
+        // TODO: populate this when we support XML Schema
+        return NULL_TYPE_INFO;
     }
 
     public void setIdAttribute(String name, boolean isId) throws DOMException {
-        throw new UnsupportedOperationException(); // TODO
+        AttrImpl attr = getAttributeNode(name);
+        if (attr == null) {
+            throw new DOMException(DOMException.NOT_FOUND_ERR,
+                    "No such attribute: " + name);
+        }
+        attr.isId = isId;
     }
 
     public void setIdAttributeNS(String namespaceURI, String localName,
             boolean isId) throws DOMException {
-        throw new UnsupportedOperationException(); // TODO
+        AttrImpl attr = getAttributeNodeNS(namespaceURI, localName);
+        if (attr == null) {
+            throw new DOMException(DOMException.NOT_FOUND_ERR,
+                    "No such attribute: " + namespaceURI +  " " + localName);
+        }
+        attr.isId = isId;
     }
 
-    public void setIdAttributeNode(Attr idAttr, boolean isId)
-            throws DOMException {
-        throw new UnsupportedOperationException(); // TODO
+    public void setIdAttributeNode(Attr idAttr, boolean isId) throws DOMException {
+        ((AttrImpl) idAttr).isId = isId;
     }
 }
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
index 2a8e1fa..8beb18c 100644
--- a/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/dom/NodeImpl.java
@@ -27,6 +27,7 @@
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.ProcessingInstruction;
+import org.w3c.dom.TypeInfo;
 import org.w3c.dom.UserDataHandler;
 
 import javax.xml.transform.TransformerException;
@@ -48,7 +49,20 @@
 public abstract class NodeImpl implements Node {
 
     private static final NodeList EMPTY_LIST = new NodeListImpl();
-    
+
+    static final TypeInfo NULL_TYPE_INFO = new TypeInfo() {
+        public String getTypeName() {
+            return null;
+        }
+        public String getTypeNamespace() {
+            return null;
+        }
+        public boolean isDerivedFrom(
+                String typeNamespaceArg, String typeNameArg, int derivationMethod) {
+            return false;
+        }
+    };
+
     DocumentImpl document;
 
     NodeImpl(DocumentImpl document) {
@@ -184,7 +198,7 @@
      * @param namespaceAware whether this node is namespace aware
      * @param namespaceURI this node's namespace URI
      */
-    protected String validatePrefix(String prefix, boolean namespaceAware, String namespaceURI) {
+    static String validatePrefix(String prefix, boolean namespaceAware, String namespaceURI) {
         if (!namespaceAware) {
             throw new DOMException(DOMException.NAMESPACE_ERR, prefix);
         }
@@ -202,6 +216,58 @@
     }
 
     /**
+     * Sets the element or attribute node to be namespace-aware and assign it
+     * the specified name and namespace URI.
+     *
+     * @param node an AttrImpl or ElementImpl node.
+     * @param namespaceURI this node's namespace URI. May be null.
+     * @param qualifiedName a possibly-prefixed name like "img" or "html:img".
+     */
+    static void setNameNS(NodeImpl node, String namespaceURI, String qualifiedName) {
+        if (qualifiedName == null) {
+            throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
+        }
+
+        String prefix = null;
+        int p = qualifiedName.lastIndexOf(":");
+        if (p != -1) {
+            prefix = validatePrefix(qualifiedName.substring(0, p), true, namespaceURI);
+            qualifiedName = qualifiedName.substring(p + 1);
+        }
+
+        if (!DocumentImpl.isXMLIdentifier(qualifiedName)) {
+            throw new DOMException(DOMException.INVALID_CHARACTER_ERR, qualifiedName);
+        }
+
+        switch (node.getNodeType()) {
+            case ATTRIBUTE_NODE:
+                if ("xmlns".equals(qualifiedName)
+                        && !"http://www.w3.org/2000/xmlns/".equals(namespaceURI)) {
+                    throw new DOMException(DOMException.NAMESPACE_ERR, qualifiedName);
+                }
+
+                AttrImpl attr = (AttrImpl) node;
+                attr.namespaceAware = true;
+                attr.namespaceURI = namespaceURI;
+                attr.prefix = prefix;
+                attr.localName = qualifiedName;
+                break;
+
+            case ELEMENT_NODE:
+                ElementImpl element = (ElementImpl) node;
+                element.namespaceAware = true;
+                element.namespaceURI = namespaceURI;
+                element.prefix = prefix;
+                element.localName = qualifiedName;
+                break;
+
+            default:
+                throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
+                        "Cannot rename nodes of type " + node.getNodeType());
+        }
+    }
+
+    /**
      * Checks whether a required string matches an actual string. This utility
      * method is used for comparing namespaces and such. It takes into account
      * null arguments and the "*" special case.
diff --git a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
index f3956f8..8bce9aa 100644
--- a/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
+++ b/xml/src/main/java/org/apache/harmony/xml/parsers/DocumentBuilderImpl.java
@@ -257,11 +257,11 @@
                 }
             } else if (token == XmlPullParser.IGNORABLE_WHITESPACE) {
                 /*
-                 * Found some ignorable whitespace. We simply take the token
-                 * text, but we only create a node if the client wants to see
-                 * whitespace at all.
+                 * Found some ignorable whitespace. We only add it if the client
+                 * wants to see whitespace. Whitespace before and after the
+                 * document element is always ignored.
                  */
-                if (!ignoreElementContentWhitespace) {
+                if (!ignoreElementContentWhitespace && document != node) {
                     appendText(document, node, token, parser.getText());
                 }
             } else if (token == XmlPullParser.TEXT || token == XmlPullParser.CDSECT) {
diff --git a/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp b/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
index 4721800..b893309 100644
--- a/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
+++ b/xml/src/main/native/org_apache_harmony_xml_ExpatParser.cpp
@@ -628,8 +628,8 @@
     jobject javaParser = parsingContext->object;
 
     ExpatElementName e(env, parsingContext, elementName);
-    jstring uri = e.uri();
-    jstring localName = e.localName();
+    jstring uri = parsingContext->processNamespaces ? e.uri() : emptyString;
+    jstring localName = parsingContext->processNamespaces ? e.localName() : emptyString;
     jstring qName = e.qName();
 
     stringStackPush(parsingContext, qName);
diff --git a/xml/src/test/java/org/apache/harmony/xml/JaxenXPathTestSuite.java b/xml/src/test/java/org/apache/harmony/xml/JaxenXPathTestSuite.java
new file mode 100644
index 0000000..e752fc0
--- /dev/null
+++ b/xml/src/test/java/org/apache/harmony/xml/JaxenXPathTestSuite.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.harmony.xml;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+import junit.textui.TestRunner;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathVariableResolver;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The implementation-independent part of the <a
+ * href="http://jaxen.codehaus.org/">Jaxen</a> XPath test suite, adapted for use
+ * by JUnit. To run these tests on a device:
+ * <ul>
+ *   <li>Obtain the Jaxen source from the project's website.
+ *   <li>Copy the files to a device: <code>adb shell mkdir /data/jaxen ;
+ *       adb push /home/dalvik-prebuild/jaxen /data/jaxen</code>
+ *   <li>Invoke this class' main method, passing the on-device path to the test
+ *       suite's root directory as an argument.
+ * </ul>
+ */
+public class JaxenXPathTestSuite {
+
+    private static final File DEFAULT_JAXEN_HOME
+            = new File("/home/dalvik-prebuild/jaxen");
+
+    public static void main(String[] args) throws Exception {
+        if (args.length != 1) {
+            System.out.println("Usage: JaxenXPathTestSuite <jaxen-home>");
+            return;
+        }
+
+        File jaxenHome = new File(args[0]);
+        TestRunner.run(suite(jaxenHome));
+    }
+
+    public static Test suite() throws Exception {
+        return suite(DEFAULT_JAXEN_HOME);
+    }
+
+    /**
+     * Creates a test suite from the Jaxen tests.xml catalog.
+     */
+    public static Test suite(File jaxenHome)
+            throws ParserConfigurationException, IOException, SAXException {
+
+        /*
+         * The tests.xml document has this structure:
+         *
+         * <tests>
+         *   <document url="...">
+         *     <context .../>
+         *     <context .../>
+         *     <context .../>
+         *   </document>
+         *   <document url="...">
+         *     <context .../>
+         *   </document>
+         * </tests>
+         */
+
+        File testsXml = new File(jaxenHome + "/xml/test/tests.xml");
+        Element tests = DocumentBuilderFactory.newInstance()
+                .newDocumentBuilder().parse(testsXml).getDocumentElement();
+
+        TestSuite result = new TestSuite();
+        for (Element document : elementsOf(tests.getElementsByTagName("document"))) {
+            String url = document.getAttribute("url");
+            InputSource inputSource = new InputSource("file:" + jaxenHome + "/" + url);
+            for (final Element context : elementsOf(document.getElementsByTagName("context"))) {
+                contextToTestSuite(result, url, inputSource, context);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Populates the test suite with tests from the given XML context element.
+     */
+    private static void contextToTestSuite(TestSuite suite, String url,
+            InputSource inputSource, Element element) {
+
+        /*
+         * Each context element has this structure:
+         *
+         * <context select="...">
+         *   <test .../>
+         *   <test .../>
+         *   <test .../>
+         *   <valueOf .../>
+         *   <valueOf .../>
+         *   <valueOf .../>
+         * </context>
+         */
+
+        String select = element.getAttribute("select");
+        Context context = new Context(inputSource, url, select);
+
+        XPath xpath = XPathFactory.newInstance().newXPath();
+        xpath.setXPathVariableResolver(new ElementVariableResolver(element));
+
+        for (Element test : elementsOf(element.getChildNodes())) {
+            if (test.getTagName().equals("test")) {
+                suite.addTest(createFromTest(xpath, context, test));
+
+            } else if (test.getTagName().equals("valueOf")) {
+                suite.addTest(createFromValueOf(xpath, context, test));
+
+            } else {
+                throw new UnsupportedOperationException("Unsupported test: " + context);
+            }
+        }
+    }
+
+    /**
+     * Returns the test described by the given {@code <test>} element. Such
+     * tests come in one of three varieties:
+     *
+     * <ul>
+     *   <li>Expected failures.
+     *   <li>String matches. These tests have a nested {@code <valueOf>} element
+     *       that sub-selects an expected text.
+     *   <li>Count matches. These tests specify how many nodes are expected to
+     *       match.
+     * </ul>
+     */
+    private static TestCase createFromTest(
+            final XPath xpath, final Context context, final Element element) {
+        final String select = element.getAttribute("select");
+
+        /* Such as <test exception="true" select="..." count="0"/> */
+        if (element.getAttribute("exception").equals("true")) {
+            return new XPathTest(context, select) {
+                @Override void test(Node contextNode) {
+                    try {
+                        xpath.evaluate(select, contextNode);
+                        fail("Expected exception!");
+                    } catch (XPathExpressionException expected) {
+                    }
+                }
+            };
+        }
+
+        /* a <test> with a nested <valueOf>, both of which have select attributes */
+        NodeList valueOfElements = element.getElementsByTagName("valueOf");
+        if (valueOfElements.getLength() == 1) {
+            final Element valueOf = (Element) valueOfElements.item(0);
+            final String valueOfSelect = valueOf.getAttribute("select");
+
+            return new XPathTest(context, select) {
+                @Override void test(Node contextNode) throws XPathExpressionException {
+                    Node newContext = (Node) xpath.evaluate(
+                            select, contextNode, XPathConstants.NODE);
+                    assertEquals(valueOf.getTextContent(),
+                            xpath.evaluate(valueOfSelect, newContext, XPathConstants.STRING));
+                }
+            };
+        }
+
+        /* Such as <test select="..." count="5"/> */
+        final String count = element.getAttribute("count");
+        if (count.length() > 0) {
+            return new XPathTest(context, select) {
+                @Override void test(Node contextNode) throws XPathExpressionException {
+                    NodeList result = (NodeList) xpath.evaluate(
+                            select, contextNode, XPathConstants.NODESET);
+                    assertEquals(Integer.parseInt(count), result.getLength());
+                }
+            };
+        }
+
+        throw new UnsupportedOperationException("Unsupported test: " + context);
+    }
+
+    /**
+     * Returns the test described by the given {@code <valueOf>} element. These
+     * tests select an expected text.
+     */
+    private static TestCase createFromValueOf(
+            final XPath xpath, final Context context, final Element element) {
+        final String select = element.getAttribute("select");
+        return new XPathTest(context, select) {
+            @Override void test(Node contextNode) throws XPathExpressionException {
+                assertEquals(element.getTextContent(),
+                        xpath.evaluate(select, contextNode, XPathConstants.STRING));
+            }
+        };
+    }
+
+    /**
+     * The subject of an XPath query. This is itself defined by an XPath query,
+     * so each test requires at least XPath expressions to be evaluated.
+     */
+    static class Context {
+        private final InputSource inputSource;
+        private final String url;
+        private final String select;
+
+        Context(InputSource inputSource, String url, String select) {
+            this.inputSource = inputSource;
+            this.url = url;
+            this.select = select;
+        }
+
+        Node getNode() {
+            XPath xpath = XPathFactory.newInstance().newXPath();
+            try {
+                return (Node) xpath.evaluate(select, inputSource, XPathConstants.NODE);
+            } catch (XPathExpressionException e) {
+                Error error = new AssertionFailedError("Failed to get context");
+                error.initCause(e);
+                throw error;
+            }
+        }
+
+        @Override public String toString() {
+            return url + " " + select;
+        }
+    }
+
+    /**
+     * This test evaluates an XPath expression against a context node and
+     * compares the result to a known expectation.
+     */
+    public abstract static class XPathTest extends TestCase {
+        private final Context context;
+        private final String select;
+
+        public XPathTest(Context context, String select) {
+            super("test");
+            this.context = context;
+            this.select = select;
+        }
+
+        abstract void test(Node contextNode) throws XPathExpressionException;
+
+        public final void test() throws XPathExpressionException {
+            try {
+                test(context.getNode());
+            } catch (XPathExpressionException e) {
+                if (isMissingFunction(e)) {
+                    fail(e.getCause().getMessage());
+                } else {
+                    throw e;
+                }
+            }
+        }
+
+        private boolean isMissingFunction(XPathExpressionException e) {
+            return e.getCause() != null
+                    && e.getCause().getMessage().startsWith("Could not find function");
+        }
+
+        @Override public String getName() {
+            return context + " " + select;
+        }
+    }
+
+    /**
+     * Performs XPath variable resolution by using {@code var:name="value"}
+     * attributes from the given element.
+     */
+    private static class ElementVariableResolver implements XPathVariableResolver {
+        private final Element element;
+        public ElementVariableResolver(Element element) {
+            this.element = element;
+        }
+        public Object resolveVariable(QName variableName) {
+            return element.getAttribute("var:" + variableName.getLocalPart());
+        }
+    }
+
+    private static List<Element> elementsOf(NodeList nodeList) {
+        List<Element> result = new ArrayList<Element>();
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            if (node instanceof Element) {
+                result.add((Element) node);
+            }
+        }
+        return result;
+    }
+}
diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
index ea7abed..4b4511d 100644
--- a/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
+++ b/xml/src/test/java/tests/api/javax/xml/parsers/DocumentBuilderTest.java
@@ -44,6 +44,7 @@
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 
 @TestTargetClass(DocumentBuilder.class) 
 public class DocumentBuilderTest extends TestCase {
@@ -564,8 +565,8 @@
     )
     public void test_parseLjava_lang_String() throws Exception {
         // case 1: Trivial use.
-        File f = new File(getClass().getResource("/simple.xml").getFile());
-        Document d = db.parse(f.getAbsolutePath());
+        URL resource = getClass().getResource("/simple.xml");
+        Document d = db.parse(resource.toString());
         assertNotNull(d);
 //          TBD  getXmlEncoding() is not supported
 //          assertEquals("ISO-8859-1", d.getXmlEncoding());
@@ -593,8 +594,8 @@
 
         // case 4: Try to parse incorrect xml file
         try {
-            f = new File(getClass().getResource("/wrong.xml").getFile());
-            db.parse(f.getAbsolutePath());
+            resource = getClass().getResource("/wrong.xml");
+            db.parse(resource.toString());
             fail("Expected SAXException was not thrown");
         } catch (SAXException sax) {
             // expected
diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserFactoryTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserFactoryTest.java
index a918ac2..6f050e3 100644
--- a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserFactoryTest.java
+++ b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserFactoryTest.java
@@ -177,6 +177,7 @@
         method = "newInstance",
         args = {}
     )
+    @KnownFailure("Dalvik doesn't honor system properties when choosing a SAX implementation")
     public void test_newInstance() {
         try {
             SAXParserFactory dtf = SAXParserFactory.newInstance();
@@ -316,57 +317,18 @@
         method = "setNamespaceAware",
         args = {boolean.class}
     )
-    @KnownFailure("Error in namespace feature handling (for ExpatParser)")
-    public void test_setNamespaceAwareZ() {
+    public void test_setNamespaceAwareZ() throws Exception {
+        MyHandler mh = new MyHandler();
 
         spf.setNamespaceAware(true);
-        MyHandler mh = new MyHandler();
         InputStream is = getClass().getResourceAsStream("/simple_ns.xml");
-        try {
-            spf.newSAXParser().parse(is, mh);
-        } catch(javax.xml.parsers.ParserConfigurationException pce) {
-            fail("ParserConfigurationException was thrown during parsing");
-        } catch(org.xml.sax.SAXException se) {
-            fail("SAXException was thrown during parsing");
-        } catch(IOException ioe) {
-            fail("IOException was thrown during parsing");
-        } finally {
-            try {
-                is.close();
-            } catch(Exception e) {}
-        }
+        spf.newSAXParser().parse(is, mh);
+        is.close();
+
         spf.setNamespaceAware(false);
         is = getClass().getResourceAsStream("/simple_ns.xml");
-        try {
-            is = getClass().getResourceAsStream("/simple_ns.xml");
-            spf.newSAXParser().parse(is, mh);
-        } catch(javax.xml.parsers.ParserConfigurationException pce) {
-            fail("ParserConfigurationException was thrown during parsing");
-        } catch(org.xml.sax.SAXException se) {
-            se.printStackTrace();
-            fail("SAXException was thrown during parsing");
-        } catch(IOException ioe) {
-            fail("IOException was thrown during parsing");
-        } finally {
-            try {
-                is.close();
-            } catch(Exception ioee) {}
-        }
-        is = getClass().getResourceAsStream("/simple_ns.xml");
-        try {
-            spf.setNamespaceAware(true);
-            spf.newSAXParser().parse(is, mh);
-        } catch(javax.xml.parsers.ParserConfigurationException pce) {
-            fail("ParserConfigurationException was thrown during parsing");
-        } catch(org.xml.sax.SAXException se) {
-            fail("SAXException was thrown during parsing");
-        } catch(IOException ioe) {
-            fail("IOException was thrown during parsing");
-        } finally {
-            try {
-                is.close();
-            } catch(Exception ioee) {}
-        }
+        spf.newSAXParser().parse(is, mh);
+        is.close();
     }
 
     /*   public void test_setSchemaLjavax_xml_validation_Schema() {
diff --git a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
index e6d6481..ad0b9c2 100644
--- a/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
+++ b/xml/src/test/java/tests/api/javax/xml/parsers/SAXParserTest.java
@@ -669,6 +669,7 @@
         method = "parse",
         args = {java.io.InputStream.class, org.xml.sax.helpers.DefaultHandler.class, java.lang.String.class}
     )
+    @KnownFailure("We supply optional qnames, but this test doesn't expect them")
     public void test_parseLjava_io_InputStreamLorg_xml_sax_helpers_DefaultHandlerLjava_lang_String() {
         for(int i = 0; i < list_wf.length; i++) {
             try {
diff --git a/xml/src/test/java/tests/org/w3c/dom/CreateAttributeNS.java b/xml/src/test/java/tests/org/w3c/dom/CreateAttributeNS.java
index c7e0d34..3cd0da6 100644
--- a/xml/src/test/java/tests/org/w3c/dom/CreateAttributeNS.java
+++ b/xml/src/test/java/tests/org/w3c/dom/CreateAttributeNS.java
@@ -218,12 +218,13 @@
 
         doc = (Document) load("hc_staff", builder);
 
-        boolean success = false;
+        // BEGIN android-changed
+        //     Our exception priorities differ from the spec
         try {
             doc.createAttributeNS(namespaceURI, "");
+            fail();
         } catch (DOMException ex) {
-            success = (ex.code == DOMException.NAMESPACE_ERR);
         }
-        assertTrue("throw_INVALID_CHARACTER_ERR", success);
+        // END android-changed
     }
 }
diff --git a/xml/src/test/java/tests/org/w3c/dom/CreateDocument.java b/xml/src/test/java/tests/org/w3c/dom/CreateDocument.java
index 157b394..8e1b175 100644
--- a/xml/src/test/java/tests/org/w3c/dom/CreateDocument.java
+++ b/xml/src/test/java/tests/org/w3c/dom/CreateDocument.java
@@ -305,13 +305,13 @@
 
         domImpl = builder.getDOMImplementation();
 
-        boolean success = false;
+        // BEGIN android-changed
+        //     Our exception priorities differ from the spec
         try {
             domImpl.createDocument(namespaceURI, "", docType);
+            fail();
         } catch (DOMException ex) {
-            success = (ex.code == DOMException.NAMESPACE_ERR);
         }
-        assertTrue("throw_NAMESPACE_ERR", success);
-
+        // END android-changed
     }
 }
diff --git a/xml/src/test/java/tests/org/w3c/dom/CreateElementNS.java b/xml/src/test/java/tests/org/w3c/dom/CreateElementNS.java
index 8d8bb4d..6258936 100644
--- a/xml/src/test/java/tests/org/w3c/dom/CreateElementNS.java
+++ b/xml/src/test/java/tests/org/w3c/dom/CreateElementNS.java
@@ -239,13 +239,14 @@
         doc = (Document) load("hc_staff", builder);
 
         {
-            boolean success = false;
+            // BEGIN android-changed
+            //     Our exception priorities differ from the spec
             try {
                 doc.createElementNS(namespaceURI, "");
+                fail();
             } catch (DOMException ex) {
-                success = (ex.code == DOMException.NAMESPACE_ERR);
             }
-            assertTrue("throw_NAMESPACE_ERR", success);
+            // END android-changed
         }
     }
 }
diff --git a/xml/src/test/java/tests/org/w3c/dom/DocumentCreateAttributeNS.java b/xml/src/test/java/tests/org/w3c/dom/DocumentCreateAttributeNS.java
index e46f3b3..87c8661 100644
--- a/xml/src/test/java/tests/org/w3c/dom/DocumentCreateAttributeNS.java
+++ b/xml/src/test/java/tests/org/w3c/dom/DocumentCreateAttributeNS.java
@@ -216,13 +216,15 @@
             qualifiedName = (String) qualifiedNames.get(indexN1004E);
 
             {
-                boolean success = false;
+
+                // BEGIN android-changed
+                //     Our exception priorities differ from the spec
                 try {
                     doc.createAttributeNS(namespaceURI, qualifiedName);
+                    fail();
                 } catch (DOMException ex) {
-                    success = (ex.code == DOMException.NAMESPACE_ERR);
                 }
-                assertTrue("documentcreateattributeNS04", success);
+                // END android-changed
             }
         }
     }
diff --git a/xml/src/test/java/tests/org/w3c/dom/SetAttributeNS.java b/xml/src/test/java/tests/org/w3c/dom/SetAttributeNS.java
index 58c146f..b1f24e9 100644
--- a/xml/src/test/java/tests/org/w3c/dom/SetAttributeNS.java
+++ b/xml/src/test/java/tests/org/w3c/dom/SetAttributeNS.java
@@ -126,14 +126,15 @@
         testAddr = elementList.item(0);
 
         {
-            boolean success = false;
+            // BEGIN android-changed
+            //     Our exception priorities differ from the spec
             try {
                 ((Element) /* Node */testAddr).setAttributeNS(namespaceURI,
                         qualifiedName, "newValue");
+                fail();
             } catch (DOMException ex) {
-                success = (ex.code == DOMException.NAMESPACE_ERR);
             }
-            assertTrue("throw_NAMESPACE_ERR", success);
+            // END android-changed
         }
     }
 
@@ -340,14 +341,15 @@
         testAddr = elementList.item(0);
 
         {
-            boolean success = false;
+            // BEGIN android-changed
+            //     Our exception priorities differ from the spec
             try {
                 ((Element) /* Node */testAddr).setAttributeNS(namespaceURI, "",
                         "newValue");
+                fail();
             } catch (DOMException ex) {
-                success = (ex.code == DOMException.NAMESPACE_ERR);
             }
-            assertTrue("throw_NAMESPACE_ERR", success);
+            // END android-changed
         }
     }
 }
diff --git a/xml/src/test/java/tests/xml/AllTests.java b/xml/src/test/java/tests/xml/AllTests.java
index 96b96c5..e9f833f 100644
--- a/xml/src/test/java/tests/xml/AllTests.java
+++ b/xml/src/test/java/tests/xml/AllTests.java
@@ -30,6 +30,7 @@
         suite.addTestSuite(SimpleBuilderTest.class);
         suite.addTestSuite(NodeTest.class);
         suite.addTestSuite(NormalizeTest.class);
+        suite.addTestSuite(SaxTest.class);
 
         //suite.addTest(tests.org.w3c.dom.AllTests.suite());
         suite.addTest(tests.api.javax.xml.parsers.AllTests.suite());
diff --git a/xml/src/test/java/tests/xml/DeclarationTest.java b/xml/src/test/java/tests/xml/DeclarationTest.java
index 8fea844..9fc42fe 100644
--- a/xml/src/test/java/tests/xml/DeclarationTest.java
+++ b/xml/src/test/java/tests/xml/DeclarationTest.java
@@ -16,6 +16,7 @@
 
 package tests.xml;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.TestCase;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
@@ -78,18 +79,21 @@
         assertEquals("ISO-8859-1", documentB.getInputEncoding());
     }
 
+    @KnownFailure("Dalvik doesn't parse the XML declaration")
     public void testGetXmlEncoding() throws Exception {
         String message = "This implementation doesn't parse the encoding from the XML declaration";
         assertEquals(message, "ISO-8859-1", documentA.getXmlEncoding());
         assertEquals(message, "US-ASCII", documentB.getXmlEncoding());
     }
 
+    @KnownFailure("Dalvik doesn't parse the XML declaration")
     public void testGetXmlVersion() throws Exception {
         String message = "This implementation doesn't parse the version from the XML declaration";
         assertEquals(message, "1.0", documentA.getXmlVersion());
         assertEquals(message, "1.1", documentB.getXmlVersion());
     }
 
+    @KnownFailure("Dalvik doesn't parse the XML declaration")
     public void testGetXmlStandalone() throws Exception {
         String message = "This implementation doesn't parse standalone from the XML declaration";
         assertEquals(message, false, documentA.getXmlStandalone());
diff --git a/xml/src/test/java/tests/xml/DomTest.java b/xml/src/test/java/tests/xml/DomTest.java
index 0f67c64..1f723f7 100644
--- a/xml/src/test/java/tests/xml/DomTest.java
+++ b/xml/src/test/java/tests/xml/DomTest.java
@@ -16,6 +16,7 @@
 
 package tests.xml;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.AssertionFailedError;
 import junit.framework.TestCase;
 import org.w3c.dom.Attr;
@@ -34,8 +35,10 @@
 import org.w3c.dom.Notation;
 import org.w3c.dom.ProcessingInstruction;
 import org.w3c.dom.Text;
+import org.w3c.dom.TypeInfo;
 import org.w3c.dom.UserDataHandler;
 import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -47,6 +50,7 @@
 import javax.xml.transform.stream.StreamResult;
 import java.io.File;
 import java.io.FileWriter;
+import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.ArrayList;
@@ -60,6 +64,7 @@
 import static org.w3c.dom.UserDataHandler.NODE_ADOPTED;
 import static org.w3c.dom.UserDataHandler.NODE_CLONED;
 import static org.w3c.dom.UserDataHandler.NODE_IMPORTED;
+import static org.w3c.dom.UserDataHandler.NODE_RENAMED;
 
 /**
  * Construct a DOM and then interrogate it.
@@ -181,6 +186,7 @@
      * Android's parsed DOM doesn't include entity declarations. These nodes will
      * only be tested for implementations that support them.
      */
+    @KnownFailure("Dalvik doesn't parse entity declarations")
     public void testEntityDeclarations() {
         assertNotNull("This implementation does not parse entity declarations", sp);
     }
@@ -189,6 +195,7 @@
      * Android's parsed DOM doesn't include notations. These nodes will only be
      * tested for implementations that support them.
      */
+    @KnownFailure("Dalvik doesn't parse notations")
     public void testNotations() {
         assertNotNull("This implementation does not parse notations", png);
     }
@@ -511,25 +518,25 @@
     }
 
     public void testCoreFeature() {
-        assertTrue(domImplementation.hasFeature("Core", null));
-        assertTrue(domImplementation.hasFeature("Core", ""));
-        assertTrue(domImplementation.hasFeature("Core", "1.0"));
-        assertTrue(domImplementation.hasFeature("Core", "2.0"));
-        assertTrue(domImplementation.hasFeature("Core", "3.0"));
-        assertTrue(domImplementation.hasFeature("CORE", "3.0"));
-        assertTrue(domImplementation.hasFeature("+Core", "3.0"));
-        assertFalse(domImplementation.hasFeature("Core", "4.0"));
+        assertFeature("Core", null);
+        assertFeature("Core", "");
+        assertFeature("Core", "1.0");
+        assertFeature("Core", "2.0");
+        assertFeature("Core", "3.0");
+        assertFeature("CORE", "3.0");
+        assertFeature("+Core", "3.0");
+        assertNoFeature("Core", "4.0");
     }
 
     public void testXmlFeature() {
-        assertTrue(domImplementation.hasFeature("XML", null));
-        assertTrue(domImplementation.hasFeature("XML", ""));
-        assertTrue(domImplementation.hasFeature("XML", "1.0"));
-        assertTrue(domImplementation.hasFeature("XML", "2.0"));
-        assertTrue(domImplementation.hasFeature("XML", "3.0"));
-        assertTrue(domImplementation.hasFeature("Xml", "3.0"));
-        assertTrue(domImplementation.hasFeature("+XML", "3.0"));
-        assertFalse(domImplementation.hasFeature("XML", "4.0"));
+        assertFeature("XML", null);
+        assertFeature("XML", "");
+        assertFeature("XML", "1.0");
+        assertFeature("XML", "2.0");
+        assertFeature("XML", "3.0");
+        assertFeature("Xml", "3.0");
+        assertFeature("+XML", "3.0");
+        assertNoFeature("XML", "4.0");
     }
 
     /**
@@ -537,26 +544,37 @@
      * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Document3-version
      */
     public void testXmlVersionFeature() {
-        String message = "This implementation does not support the XMLVersion feature";
-        assertTrue(message, domImplementation.hasFeature("XMLVersion", null));
-        assertTrue(message, domImplementation.hasFeature("XMLVersion", ""));
-        assertTrue(message, domImplementation.hasFeature("XMLVersion", "1.0"));
-        assertTrue(message, domImplementation.hasFeature("XMLVersion", "1.1"));
-        assertTrue(message, domImplementation.hasFeature("XMLVERSION", "1.1"));
-        assertTrue(message, domImplementation.hasFeature("+XMLVersion", "1.1"));
-        assertFalse(domImplementation.hasFeature("XMLVersion", "1.2"));
-        assertFalse(domImplementation.hasFeature("XMLVersion", "2.0"));
-        assertFalse(domImplementation.hasFeature("XMLVersion", "2.0"));
+        assertFeature("XMLVersion", null);
+        assertFeature("XMLVersion", "");
+        assertFeature("XMLVersion", "1.0");
+        assertFeature("XMLVersion", "1.1");
+        assertFeature("XMLVERSION", "1.1");
+        assertFeature("+XMLVersion", "1.1");
+        assertNoFeature("XMLVersion", "1.2");
+        assertNoFeature("XMLVersion", "2.0");
+        assertNoFeature("XMLVersion", "2.0");
     }
 
-    public void testLsFeature() {
-        assertTrue("This implementation does not support the LS feature",
-                domImplementation.hasFeature("LS", "3.0"));
+    @KnownFailure("Dalvik doesn't support load/save")
+    public void testLoadSaveFeature() {
+        assertFeature("LS", "3.0");
     }
 
+    @KnownFailure("Dalvik doesn't support the element traversal feature")
     public void testElementTraversalFeature() {
-        assertTrue("This implementation does not support the ElementTraversal feature",
-                domImplementation.hasFeature("ElementTraversal", "1.0"));
+        assertFeature("ElementTraversal", "1.0");
+    }
+
+    private void assertFeature(String feature, String version) {
+        String message = "This implementation is expected to support "
+                + feature + " v. " + version + " but does not.";
+        assertTrue(message, domImplementation.hasFeature(feature, version));
+        assertNotNull(message, domImplementation.getFeature(feature, version));
+    }
+
+    private void assertNoFeature(String feature, String version) {
+        assertFalse(domImplementation.hasFeature(feature, version));
+        assertNull(domImplementation.getFeature(feature, version));
     }
 
     public void testIsSupported() {
@@ -639,6 +657,7 @@
         assertFalse(text.isElementContentWhitespace());
     }
 
+    @KnownFailure("Dalvik doesn't recognize element content whitespace")
     public void testIsElementContentWhitespaceWithDeclaration() throws Exception {
         String xml = "<!DOCTYPE menu [\n"
                 + "  <!ELEMENT menu (item)*>\n"
@@ -672,6 +691,7 @@
         assertEquals("60%", vitamincText.getWholeText());
     }
 
+    @KnownFailure("Dalvik doesn't resolve entity references")
     public void testGetWholeTextWithEntityReference() {
         EntityReference spReference = document.createEntityReference("sp");
         description.insertBefore(spReference, descriptionText2);
@@ -1138,6 +1158,189 @@
         assertNull(option2Reference.getBaseURI());
     }
 
+    public void testProgrammaticElementIds() {
+        vitaminc.setAttribute("name", "c");
+        assertFalse(vitaminc.getAttributeNode("name").isId());
+        assertNull(document.getElementById("c"));
+
+        // set the ID attribute...
+        vitaminc.setIdAttribute("name", true);
+        assertTrue(vitaminc.getAttributeNode("name").isId());
+        assertSame(vitaminc, document.getElementById("c"));
+
+        // ... and then take it away
+        vitaminc.setIdAttribute("name", false);
+        assertFalse(vitaminc.getAttributeNode("name").isId());
+        assertNull(document.getElementById("c"));
+    }
+
+    public void testMultipleIdsOnOneElement() {
+        vitaminc.setAttribute("name", "c");
+        vitaminc.setIdAttribute("name", true);
+        vitaminc.setAttribute("atc", "a11g");
+        vitaminc.setIdAttribute("atc", true);
+
+        assertTrue(vitaminc.getAttributeNode("name").isId());
+        assertTrue(vitaminc.getAttributeNode("atc").isId());
+        assertSame(vitaminc, document.getElementById("c"));
+        assertSame(vitaminc, document.getElementById("a11g"));
+        assertNull(document.getElementById("g"));
+    }
+
+    @KnownFailure("Dalvik treats id attributes as identifiers")
+    public void testAttributeNamedIdIsNotAnIdByDefault() {
+        String message = "This implementation incorrectly interprets the "
+                + "\"id\" attribute as an identifier by default.";
+        vitaminc.setAttribute("id", "c");
+        assertNull(message, document.getElementById("c"));
+    }
+
+    public void testElementTypeInfo() {
+        TypeInfo typeInfo = description.getSchemaTypeInfo();
+        assertNull(typeInfo.getTypeName());
+        assertNull(typeInfo.getTypeNamespace());
+        assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION));
+    }
+
+    public void testAttributeTypeInfo() {
+        TypeInfo typeInfo = standard.getSchemaTypeInfo();
+        assertNull(typeInfo.getTypeName());
+        assertNull(typeInfo.getTypeNamespace());
+        assertFalse(typeInfo.isDerivedFrom("x", "y", TypeInfo.DERIVATION_UNION));
+    }
+
+    public void testRenameElement() {
+        document.renameNode(description, null, "desc");
+        assertEquals("desc", description.getTagName());
+        assertEquals("desc", description.getLocalName());
+        assertEquals(null, description.getPrefix());
+        assertEquals(null, description.getNamespaceURI());
+    }
+
+    public void testRenameElementWithPrefix() {
+        try {
+            document.renameNode(description, null, "a:desc");
+            fail();
+        } catch (DOMException e) {
+        }
+    }
+
+    public void testRenameElementWithNamespace() {
+        document.renameNode(description, "http://sales", "desc");
+        assertEquals("desc", description.getTagName());
+        assertEquals("desc", description.getLocalName());
+        assertEquals(null, description.getPrefix());
+        assertEquals("http://sales", description.getNamespaceURI());
+    }
+
+    public void testRenameElementWithPrefixAndNamespace() {
+        document.renameNode(description, "http://sales", "a:desc");
+        assertEquals("a:desc", description.getTagName());
+        assertEquals("desc", description.getLocalName());
+        assertEquals("a", description.getPrefix());
+        assertEquals("http://sales", description.getNamespaceURI());
+    }
+
+    public void testRenameAttribute() {
+        document.renameNode(deluxe, null, "special");
+        assertEquals("special", deluxe.getName());
+        assertEquals("special", deluxe.getLocalName());
+        assertEquals(null, deluxe.getPrefix());
+        assertEquals(null, deluxe.getNamespaceURI());
+    }
+
+    public void testRenameAttributeWithPrefix() {
+        try {
+            document.renameNode(deluxe, null, "a:special");
+            fail();
+        } catch (DOMException e) {
+        }
+    }
+
+    public void testRenameAttributeWithNamespace() {
+        document.renameNode(deluxe, "http://sales", "special");
+        assertEquals("special", deluxe.getName());
+        assertEquals("special", deluxe.getLocalName());
+        assertEquals(null, deluxe.getPrefix());
+        assertEquals("http://sales", deluxe.getNamespaceURI());
+    }
+
+    public void testRenameAttributeWithPrefixAndNamespace() {
+        document.renameNode(deluxe, "http://sales", "a:special");
+        assertEquals("a:special", deluxe.getName());
+        assertEquals("special", deluxe.getLocalName());
+        assertEquals("a", deluxe.getPrefix());
+        assertEquals("http://sales", deluxe.getNamespaceURI());
+    }
+
+    public void testUserDataHandlerNotifiedOfRenames() {
+        RecordingHandler handler = new RecordingHandler();
+        description.setUserData("a", "apple", handler);
+        deluxe.setUserData("b", "banana", handler);
+        standard.setUserData("c", "cat", handler);
+
+        document.renameNode(deluxe, null, "special");
+        document.renameNode(description, null, "desc");
+
+        Set<String> expected = new HashSet<String>();
+        expected.add(notification(NODE_RENAMED, "a", "apple", description, null));
+        expected.add(notification(NODE_RENAMED, "b", "banana", deluxe, null));
+        assertEquals(expected, handler.calls);
+    }
+
+    public void testRenameToInvalid() {
+        try {
+            document.renameNode(description, null, "xmlns:foo");
+            fail();
+        } catch (DOMException e) {
+        }
+        try {
+            document.renameNode(description, null, "xml:foo");
+            fail();
+        } catch (DOMException e) {
+        }
+        try {
+            document.renameNode(deluxe, null, "xmlns");
+            fail();
+        } catch (DOMException e) {
+        }
+    }
+
+    public void testRenameNodeOtherThanElementOrAttribute() {
+        for (Node node : allNodes) {
+            if (node.getNodeType() == Node.ATTRIBUTE_NODE
+                    || node.getNodeType() == Node.ELEMENT_NODE) {
+                continue;
+            }
+
+            try {
+                document.renameNode(node, null, "foo");
+                fail();
+            } catch (DOMException e) {
+            }
+        }
+    }
+
+    public void testDocumentDoesNotHaveWhitespaceChildren()
+            throws IOException, SAXException {
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>  \n"
+                + "   <foo/>\n"
+                + "  \n";
+        document = builder.parse(new InputSource(new StringReader(xml)));
+        assertEquals("Document nodes shouldn't have text children",
+                1, document.getChildNodes().getLength());
+    }
+
+    @KnownFailure("Dalvik document nodes accept arbitrary child nodes")
+    public void testDocumentAddChild()
+            throws IOException, SAXException {
+        try {
+            document.appendChild(document.createTextNode("   "));
+            fail("Document nodes shouldn't accept child nodes");
+        } catch (DOMException e) {
+        }
+    }
+
     private class RecordingHandler implements UserDataHandler {
         final Set<String> calls = new HashSet<String>();
         public void handle(short operation, String key, Object data, Node src, Node dst) {
diff --git a/xml/src/test/java/tests/xml/NodeTest.java b/xml/src/test/java/tests/xml/NodeTest.java
index dc3a333..d1d99c1 100644
--- a/xml/src/test/java/tests/xml/NodeTest.java
+++ b/xml/src/test/java/tests/xml/NodeTest.java
@@ -51,14 +51,13 @@
         File file = Support_Resources.resourceToTempFile("/simple.xml");
         Document document = builder.parse(file);
 
-        String baseUri = "file:" + file.getPath();
-        assertEquals(baseUri, document.getBaseURI());
+        assertFileUriEquals(file, document.getBaseURI());
 
         Element documentElement = document.getDocumentElement();
         for (Node node : flattenSubtree(documentElement)) {
             if (node.getNodeType() == Node.ELEMENT_NODE
                     || node.getNodeType() == Node.DOCUMENT_NODE) {
-                assertEquals("Unexpected base URI for " + node, baseUri, node.getBaseURI());
+                assertFileUriEquals(file, node.getBaseURI());
             } else {
                 assertNull("Unexpected base URI for " + node, node.getBaseURI());
             }
@@ -69,6 +68,12 @@
         // TODO: test URI santization
     }
 
+    private void assertFileUriEquals(File expectedFile, String actual) {
+        assertTrue("Expected URI for: " + expectedFile + " but was " + actual + ". ",
+                actual.equals("file:" + expectedFile)
+                        || actual.equals("file://" + expectedFile));
+    }
+
     private List<Node> flattenSubtree(Node subtree) {
         List<Node> result = new ArrayList<Node>();
         traverse(subtree, result);
diff --git a/xml/src/test/java/tests/xml/NormalizeTest.java b/xml/src/test/java/tests/xml/NormalizeTest.java
index f35ca10..5d2bb0be 100644
--- a/xml/src/test/java/tests/xml/NormalizeTest.java
+++ b/xml/src/test/java/tests/xml/NormalizeTest.java
@@ -16,6 +16,7 @@
 
 package tests.xml;
 
+import dalvik.annotation.KnownFailure;
 import junit.framework.TestCase;
 import org.w3c.dom.CDATASection;
 import org.w3c.dom.Comment;
@@ -204,6 +205,7 @@
      * This fails under the RI because setParameter() succeeds even though
      * canSetParameter() returns false.
      */
+    @KnownFailure("Dalvik doesn't honor the schema-type parameter")
     public void testSchemaTypeDtd() {
         assertUnsupported("schema-type", "http://www.w3.org/TR/REC-xml"); // supported in RI v6
     }
diff --git a/xml/src/test/java/tests/xml/SaxTest.java b/xml/src/test/java/tests/xml/SaxTest.java
new file mode 100644
index 0000000..dc59b2d
--- /dev/null
+++ b/xml/src/test/java/tests/xml/SaxTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tests.xml;
+
+import dalvik.annotation.KnownFailure;
+import junit.framework.TestCase;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Initiate and observe a SAX parse session.
+ */
+public class SaxTest extends TestCase {
+
+    public void testNoPrefixesNoNamespaces() throws Exception {
+        parse(false, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("bar", "", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(false, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("a:bar", "", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    public void testNoPrefixesYesNamespaces() throws Exception {
+        parse(false, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("foo", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(false, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("http://quux", uri);
+                assertEquals("foo", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("http://quux", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    /**
+     * Android's Expat-based SAX parser fails this test because Expat doesn't
+     * supply us with our much desired {@code xmlns="http://..."} attributes.
+     */
+    @KnownFailure("No xmlns attributes from Expat")
+    public void testYesPrefixesYesNamespaces() throws Exception {
+        parse(true, true, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("foo", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(true, true, "<a:foo a:bar=\"baz\" xmlns:a=\"http://quux\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("http://quux", uri);
+                assertEquals("foo", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(2, attributes.getLength());
+                assertEquals("http://quux", attributes.getURI(0));
+                assertEquals("bar", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+                assertEquals("", attributes.getURI(1));
+                assertEquals("", attributes.getLocalName(1));
+                assertEquals("xmlns:a", attributes.getQName(1));
+            }
+        });
+    }
+
+    public void testYesPrefixesNoNamespaces() throws Exception {
+        parse(true, false, "<foo bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("bar", "", attributes.getLocalName(0));
+                assertEquals("bar", attributes.getQName(0));
+            }
+        });
+
+        parse(true, false, "<a:foo a:bar=\"baz\"/>", new DefaultHandler() {
+            @Override public void startElement(String uri, String localName,
+                    String qName, Attributes attributes) {
+                assertEquals("", uri);
+                assertEquals("", localName);
+                assertEquals("a:foo", qName);
+                assertEquals(1, attributes.getLength());
+                assertEquals("", attributes.getURI(0));
+                assertOneOf("a:bar", "", attributes.getLocalName(0));
+                assertEquals("a:bar", attributes.getQName(0));
+            }
+        });
+    }
+
+    private void parse(boolean prefixes, boolean namespaces, String xml,
+            ContentHandler handler) throws Exception {
+        SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+        XMLReader reader = parser.getXMLReader();
+        reader.setFeature("http://xml.org/sax/features/namespace-prefixes", prefixes);
+        reader.setFeature("http://xml.org/sax/features/namespaces", namespaces);
+        reader.setContentHandler(handler);
+        reader.parse(new InputSource(new StringReader(xml)));
+    }
+
+    /**
+     * @param expected an optional value that may or may have not been supplied
+     * @param sentinel a marker value that means the expected value was omitted
+     */
+    private void assertOneOf(String expected, String sentinel, String actual) {
+        List<String> optionsList = Arrays.asList(sentinel, expected);
+        assertTrue("Expected one of " + optionsList + " but was " + actual,
+                optionsList.contains(actual));
+    }
+}