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> <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> <small>(comma)</small> elision.</li>
- * <li>Strings may be quoted with <code>'</code> <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> <small>(left bracket)</small>
- * and ends with <code>]</code> <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> <small>(left bracket)</small> and ending
- * with <code>]</code> <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> <small>(comma)</small> may appear just
- * before the closing brace.</li>
- * <li>Strings may be quoted with <code>'</code> <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> <small>(left brace)</small> and ending
- * with <code>}</code> <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 </, 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> <small>(left brace)</small> and ending
- * with <code>}</code> <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> <small>(left brace)</small> and ending
- * with <code>}</code> <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> <small>(left brace)</small> and ending
- * with <code>}</code> <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> <small>(left brace)</small> and ending
- * with <code>}</code> <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> <small>(left brace)</small> and ending
- * with <code>}</code> <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> <small>(double quote)</small> or
- * <code>'</code> <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 — 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 – as
+ * Android devices typically don't – 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));
+ }
+}