summaryrefslogtreecommitdiffstats
path: root/json/src
diff options
context:
space:
mode:
Diffstat (limited to 'json/src')
-rw-r--r--json/src/rewrite/java/org/json/JSON.java112
-rw-r--r--json/src/rewrite/java/org/json/JSONArray.java322
-rw-r--r--json/src/rewrite/java/org/json/JSONException.java49
-rw-r--r--json/src/rewrite/java/org/json/JSONObject.java395
-rw-r--r--json/src/rewrite/java/org/json/JSONStringer.java340
-rw-r--r--json/src/rewrite/java/org/json/JSONTokener.java480
-rw-r--r--json/src/test/java/org/json/AllTests.java1
-rw-r--r--json/src/test/java/org/json/JSONArrayTest.java120
-rw-r--r--json/src/test/java/org/json/JSONObjectTest.java145
-rw-r--r--json/src/test/java/org/json/JSONStringerTest.java60
-rw-r--r--json/src/test/java/org/json/JSONTokenerTest.java204
-rw-r--r--json/src/test/java/org/json/ParsingTest.java260
-rw-r--r--json/src/test/java/org/json/SelfUseTest.java35
13 files changed, 2362 insertions, 161 deletions
diff --git a/json/src/rewrite/java/org/json/JSON.java b/json/src/rewrite/java/org/json/JSON.java
new file mode 100644
index 0000000..029884b
--- /dev/null
+++ b/json/src/rewrite/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 Double.valueOf((String) value).intValue();
+ } 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 Double.valueOf((String) value).longValue();
+ } 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/rewrite/java/org/json/JSONArray.java b/json/src/rewrite/java/org/json/JSONArray.java
new file mode 100644
index 0000000..fa42054
--- /dev/null
+++ b/json/src/rewrite/java/org/json/JSONArray.java
@@ -0,0 +1,322 @@
+/*
+ * 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 java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * An indexed sequence of JSON-safe values.
+ */
+public class JSONArray {
+
+ private final List<Object> values;
+
+ public JSONArray() {
+ values = new ArrayList<Object>();
+ }
+
+ /* Accept a raw type for API compatibility */
+ public JSONArray(Collection copyFrom) {
+ this();
+ Collection<?> copyFromTyped = (Collection<?>) copyFrom;
+ values.addAll(copyFromTyped);
+ }
+
+ 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");
+ }
+ }
+
+ public JSONArray(String json) throws JSONException {
+ this(new JSONTokener(json));
+ }
+
+ public int length() {
+ return values.size();
+ }
+
+ public JSONArray put(boolean value) {
+ values.add(value);
+ return this;
+ }
+
+ public JSONArray put(double value) throws JSONException {
+ values.add(JSON.checkDouble(value));
+ return this;
+ }
+
+ public JSONArray put(int value) {
+ values.add(value);
+ return this;
+ }
+
+ public JSONArray put(long value) {
+ values.add(value);
+ return this;
+ }
+
+ public JSONArray put(Object value) {
+ values.add(value);
+ return this;
+ }
+
+ public JSONArray put(int index, boolean value) throws JSONException {
+ return put(index, (Boolean) value);
+ }
+
+ public JSONArray put(int index, double value) throws JSONException {
+ return put(index, (Double) value);
+ }
+
+ public JSONArray put(int index, int value) throws JSONException {
+ return put(index, (Integer) value);
+ }
+
+ public JSONArray put(int index, long value) throws JSONException {
+ return put(index, (Long) value);
+ }
+
+ 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;
+ }
+
+ public boolean isNull(int index) {
+ Object value = opt(index);
+ return value == null || value == 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() + ")");
+ }
+ }
+
+ public Object opt(int index) {
+ if (index < 0 || index >= values.size()) {
+ return null;
+ }
+ return values.get(index);
+ }
+
+ 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;
+ }
+
+ public boolean optBoolean(int index) {
+ return optBoolean(index, false);
+ }
+
+ public boolean optBoolean(int index, boolean fallback) {
+ Object object = opt(index);
+ Boolean result = JSON.toBoolean(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public double optDouble(int index) {
+ return optDouble(index, Double.NaN);
+ }
+
+ public double optDouble(int index, double fallback) {
+ Object object = opt(index);
+ Double result = JSON.toDouble(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public int optInt(int index) {
+ return optInt(index, 0);
+ }
+
+ public int optInt(int index, int fallback) {
+ Object object = opt(index);
+ Integer result = JSON.toInteger(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public long optLong(int index) {
+ return optLong(index, 0L);
+ }
+
+ public long optLong(int index, long fallback) {
+ Object object = opt(index);
+ Long result = JSON.toLong(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public String optString(int index) {
+ return optString(index, "");
+ }
+
+ public String optString(int index, String fallback) {
+ Object object = opt(index);
+ String result = JSON.toString(object);
+ return result != null ? result : fallback;
+ }
+
+ public JSONArray getJSONArray(int index) throws JSONException {
+ Object object = get(index);
+ if (object instanceof JSONArray) {
+ return (JSONArray) object;
+ } else {
+ throw JSON.typeMismatch(index, object, "JSONArray");
+ }
+ }
+
+ public JSONArray optJSONArray(int index) {
+ Object object = opt(index);
+ return object instanceof JSONArray ? (JSONArray) object : null;
+ }
+
+ 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");
+ }
+ }
+
+ public JSONObject optJSONObject(int index) {
+ Object object = opt(index);
+ return object instanceof JSONObject ? (JSONObject) object : null;
+ }
+
+ public JSONObject toJSONObject(JSONArray names) throws JSONException {
+ JSONObject result = new JSONObject();
+ int length = Math.min(names.length(), values.size());
+ if (length == 0) {
+ return null;
+ }
+ for (int i = 0; i < length; i++) {
+ String name = JSON.toString(names.opt(i));
+ result.put(name, opt(i));
+ }
+ return result;
+ }
+
+ 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();
+ }
+
+ @Override public String toString() {
+ try {
+ JSONStringer stringer = new JSONStringer();
+ writeTo(stringer);
+ return stringer.toString();
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ 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();
+ }
+
+ @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/rewrite/java/org/json/JSONException.java b/json/src/rewrite/java/org/json/JSONException.java
new file mode 100644
index 0000000..e1efd9f
--- /dev/null
+++ b/json/src/rewrite/java/org/json/JSONException.java
@@ -0,0 +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.
+
+/**
+ * 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#NaN
+ * NaN} or {@link Double#POSITIVE_INFINITY infinity}.
+ * <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 {
+
+ public JSONException(String s) {
+ super(s);
+ }
+}
diff --git a/json/src/rewrite/java/org/json/JSONObject.java b/json/src/rewrite/java/org/json/JSONObject.java
new file mode 100644
index 0000000..c92d549
--- /dev/null
+++ b/json/src/rewrite/java/org/json/JSONObject.java
@@ -0,0 +1,395 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ *
+ *
+ * <p>TODO: Note about self-use
+ */
+public class JSONObject {
+
+ private static final Double NEGATIVE_ZERO = -0d;
+
+ public static final Object NULL = new Object() {
+ @Override public boolean equals(Object o) {
+ return o == this || o == null; // API specifies this broken equals implementation
+ }
+ @Override public String toString() {
+ return "null";
+ }
+ };
+
+ private final Map<String, Object> nameValuePairs;
+
+ public JSONObject() {
+ nameValuePairs = new HashMap<String, Object>();
+ }
+
+ /* Accept a raw type for API compatibility */
+ public JSONObject(Map copyFrom) {
+ this();
+ Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
+ for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
+ /*
+ * Deviate from the original by checking that keys are non-null and
+ * of the proper type. (We still defer validating the values).
+ */
+ String key = (String) entry.getKey();
+ if (key == null) {
+ throw new NullPointerException();
+ }
+ nameValuePairs.put(key, entry.getValue());
+ }
+ }
+
+ 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 {
+ throw JSON.typeMismatch(object, "JSONObject");
+ }
+ }
+
+ public JSONObject(String json) throws JSONException {
+ this(new JSONTokener(json));
+ }
+
+ 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);
+ }
+ }
+ }
+
+ public int length() {
+ return nameValuePairs.size();
+ }
+
+ public JSONObject put(String name, boolean value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ public JSONObject put(String name, double value) throws JSONException {
+ nameValuePairs.put(checkName(name), JSON.checkDouble(value));
+ return this;
+ }
+
+ public JSONObject put(String name, int value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ public JSONObject put(String name, long value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ public JSONObject put(String name, Object value) throws JSONException {
+ if (value == null) {
+ nameValuePairs.remove(name);
+ return this;
+ }
+ 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;
+ }
+
+ public JSONObject putOpt(String name, Object value) throws JSONException {
+ if (name == null || value == null) {
+ return this;
+ }
+ return put(name, value);
+ }
+
+ public JSONObject accumulate(String name, Object value) throws JSONException {
+ Object current = nameValuePairs.get(checkName(name));
+ if (current == null) {
+ put(name, value);
+ } else if (current instanceof JSONArray) {
+ JSONArray array = (JSONArray) current;
+ array.put(value);
+ } else {
+ JSONArray array = new JSONArray();
+ array.put(current);
+ array.put(value); // fails on bogus values
+ nameValuePairs.put(name, array);
+ }
+ return this;
+ }
+
+ String checkName(String name) throws JSONException {
+ if (name == null) {
+ throw new JSONException("Names must be non-null");
+ }
+ return name;
+ }
+
+ public Object remove(String name) {
+ return nameValuePairs.remove(name);
+ }
+
+ public boolean isNull(String name) {
+ Object value = nameValuePairs.get(name);
+ return value == null || value == NULL;
+ }
+
+ public boolean has(String name) {
+ return nameValuePairs.containsKey(name);
+ }
+
+ public Object get(String name) throws JSONException {
+ Object result = nameValuePairs.get(name);
+ if (result == null) {
+ throw new JSONException("No value for " + name);
+ }
+ return result;
+ }
+
+ public Object opt(String name) {
+ return nameValuePairs.get(name);
+ }
+
+ 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;
+ }
+
+ public boolean optBoolean(String name) {
+ return optBoolean(name, false);
+ }
+
+ public boolean optBoolean(String name, boolean fallback) {
+ Object object = opt(name);
+ Boolean result = JSON.toBoolean(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public double optDouble(String name) {
+ return optDouble(name, Double.NaN);
+ }
+
+ public double optDouble(String name, double fallback) {
+ Object object = opt(name);
+ Double result = JSON.toDouble(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public int optInt(String name) {
+ return optInt(name, 0);
+ }
+
+ public int optInt(String name, int fallback) {
+ Object object = opt(name);
+ Integer result = JSON.toInteger(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public long optLong(String name) {
+ return optLong(name, 0L);
+ }
+
+ public long optLong(String name, long fallback) {
+ Object object = opt(name);
+ Long result = JSON.toLong(object);
+ return result != null ? result : fallback;
+ }
+
+ 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;
+ }
+
+ public String optString(String name) {
+ return optString(name, "");
+ }
+
+ public String optString(String name, String fallback) {
+ Object object = opt(name);
+ String result = JSON.toString(object);
+ return result != null ? result : fallback;
+ }
+
+ 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");
+ }
+ }
+
+ public JSONArray optJSONArray(String name) {
+ Object object = opt(name);
+ return object instanceof JSONArray ? (JSONArray) object : null;
+ }
+
+ 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");
+ }
+ }
+
+ public JSONObject optJSONObject(String name) {
+ Object object = opt(name);
+ return object instanceof JSONObject ? (JSONObject) object : null;
+ }
+
+ public JSONArray toJSONArray(JSONArray names) throws JSONException {
+ JSONArray result = new JSONArray();
+ if (names == null) {
+ return null;
+ }
+ int length = names.length();
+ if (length == 0) {
+ return null;
+ }
+ for (int i = 0; i < length; i++) {
+ String name = JSON.toString(names.opt(i));
+ result.put(opt(name));
+ }
+ return result;
+ }
+
+ /* Return a raw type for API compatibility */
+ public Iterator keys() {
+ return nameValuePairs.keySet().iterator();
+ }
+
+ public JSONArray names() {
+ return nameValuePairs.isEmpty()
+ ? null
+ : new JSONArray(new ArrayList<String>(nameValuePairs.keySet()));
+ }
+
+ @Override public String toString() {
+ try {
+ JSONStringer stringer = new JSONStringer();
+ writeTo(stringer);
+ return stringer.toString();
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ public String toString(int indentSpaces) throws JSONException {
+ JSONStringer stringer = new JSONStringer(indentSpaces);
+ writeTo(stringer);
+ return stringer.toString();
+ }
+
+ void writeTo(JSONStringer stringer) throws JSONException {
+ stringer.object();
+ for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
+ stringer.key(entry.getKey()).value(entry.getValue());
+ }
+ stringer.endObject();
+ }
+
+ public static String numberToString(Number number) throws JSONException {
+ if (number == null) {
+ throw new JSONException("Number must be non-null");
+ }
+
+ 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";
+ }
+
+ long longValue = number.longValue();
+ if (doubleValue == (double) longValue) {
+ return Long.toString(longValue);
+ }
+
+ return number.toString();
+ }
+
+ public static String quote(String data) {
+ if (data == null) {
+ return "\"\"";
+ }
+ 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();
+ }
+ }
+}
diff --git a/json/src/rewrite/java/org/json/JSONStringer.java b/json/src/rewrite/java/org/json/JSONStringer.java
new file mode 100644
index 0000000..fb60bd1
--- /dev/null
+++ b/json/src/rewrite/java/org/json/JSONStringer.java
@@ -0,0 +1,340 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ *
+ */
+public class JSONStringer {
+
+ /** The output data, containing at most one top-level array or object. */
+ final StringBuilder out = new StringBuilder();
+
+ /**
+ * Lexical scoping elements within this stringer, necessary to insert the
+ * appropriate separator characters (ie. commas and colons) and to detect
+ * nesting errors.
+ */
+ 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,
+ }
+
+ /**
+ * 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);
+ }
+
+ public JSONStringer array() throws JSONException {
+ return open(Scope.EMPTY_ARRAY, "[");
+ }
+
+ public JSONStringer endArray() throws JSONException {
+ return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
+ }
+
+ public JSONStringer object() throws JSONException {
+ return open(Scope.EMPTY_OBJECT, "{");
+ }
+
+ 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");
+ }
+ 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);
+ }
+
+ 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;
+
+ } else if (value instanceof JSONObject) {
+ ((JSONObject) value).writeTo(this);
+ return this;
+ }
+
+ 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());
+ }
+
+ return this;
+ }
+
+ public JSONStringer value(boolean value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ beforeValue();
+ out.append(value);
+ return this;
+ }
+
+ public JSONStringer value(double value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ beforeValue();
+ out.append(JSONObject.numberToString(value));
+ return this;
+ }
+
+ 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);
+ }
+ }
+
+ 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");
+ }
+ }
+
+ /**
+ * 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/rewrite/java/org/json/JSONTokener.java b/json/src/rewrite/java/org/json/JSONTokener.java
new file mode 100644
index 0000000..e249c74
--- /dev/null
+++ b/json/src/rewrite/java/org/json/JSONTokener.java
@@ -0,0 +1,480 @@
+/*
+ * 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.
+
+/**
+ *
+ */
+public class JSONTokener {
+
+ /** The input JSON. */
+ private final String in;
+
+ /**
+ * 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 pos;
+
+ public JSONTokener(String in) {
+ this.in = in;
+ }
+
+ public Object nextValue() throws JSONException {
+ int c = nextCleanInternal();
+ switch (c) {
+ case -1:
+ throw syntaxError("End of input");
+
+ case '{':
+ return readObject();
+
+ 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;
+ }
+
+ char peek = in.charAt(pos);
+ if (peek != '*' && peek != '/') {
+ return c;
+ }
+
+ skipComment();
+ continue;
+
+ default:
+ return c;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Advances the position until it is beyond the current comment. The opening
+ * slash '/' should have already been read, and character at the current
+ * position be an asterisk '*' for a C-style comment or a slash '/' for an
+ * end-of-line comment.
+ *
+ * @throws JSONException if a C-style comment was not terminated.
+ */
+ private void skipComment() throws JSONException {
+ if (in.charAt(pos++) == '*') {
+ int commentEnd = in.indexOf("*/", pos);
+ if (commentEnd == -1) {
+ throw syntaxError("Unterminated comment");
+ }
+ pos = commentEnd + 2;
+
+ } else {
+ /*
+ * Skip to the next newline character. If the line is terminated by
+ * "\r\n", the '\n' will be consumed as whitespace by the caller.
+ */
+ for (; pos < in.length(); pos++) {
+ char c = in.charAt(pos);
+ if (c == '\r' || c == '\n') {
+ pos++;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ public String nextString(char quote) throws JSONException {
+ /*
+ * 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();
+ }
+ }
+
+ if (c == '\\') {
+ if (pos == in.length()) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(in, start, pos - 1);
+ builder.append(readEscapeCharacter());
+ start = pos;
+ }
+ }
+
+ throw syntaxError("Unterminated string");
+ }
+
+ /**
+ * 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".
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ 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);
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * 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");
+
+ 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;
+ } else if ("false".equalsIgnoreCase(literal)) {
+ return Boolean.FALSE;
+ }
+
+ /* 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 {
+ 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 text from the current position until the first of any of the
+ * given characters or a newline character, excluding that character. The
+ * position is advanced to 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());
+ }
+ }
+
+ /*
+ * 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);
+ }
+ if (pos < in.length() && in.charAt(pos) == '>') {
+ pos++;
+ }
+
+ result.put((String) name, nextValue());
+
+ 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");
+ }
+ }
+ }
+
+ public JSONException syntaxError(String text) {
+ return new JSONException(text + this);
+ }
+
+ @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.
+ */
+
+ public boolean more() {
+ return pos < in.length();
+ }
+
+ public char next() {
+ return pos < in.length() ? in.charAt(pos++) : '\0';
+ }
+
+ public char next(char c) throws JSONException {
+ char result = next();
+ if (result != c) {
+ throw syntaxError("Expected " + c + " but was " + result);
+ }
+ return result;
+ }
+
+ public char nextClean() throws JSONException {
+ int nextCleanInt = nextCleanInternal();
+ return nextCleanInt == -1 ? '\0' : (char) nextCleanInt;
+ }
+
+ /**
+ * TODO: note about how this method returns a substring, and could cause a memory leak
+ */
+ 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;
+ }
+
+ /**
+ * TODO: note about how this method returns a substring, and could cause a memory leak
+ */
+ public String nextTo(String excluded) {
+ if (excluded == null) {
+ throw new NullPointerException();
+ }
+ return nextToInternal(excluded).trim();
+ }
+
+ /**
+ * TODO: note about how this method returns a substring, and could cause a memory leak
+ */
+ public String nextTo(char excluded) {
+ return nextToInternal(String.valueOf(excluded)).trim();
+ }
+
+ public void skipPast(String thru) {
+ int thruStart = in.indexOf(thru, pos);
+ pos = thruStart == -1 ? in.length() : (thruStart + thru.length());
+ }
+
+ public char skipTo(char to) {
+ for (int i = pos, length = in.length(); i < length; i++) {
+ if (in.charAt(i) == to) {
+ pos = i;
+ return to;
+ }
+ }
+ return '\0';
+ }
+
+ public void back() {
+ if (--pos == -1) {
+ pos = 0;
+ }
+ }
+
+ 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 @@ public class AllTests {
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..485a729 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 junit.framework.TestCase;
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 @@ public class JSONArrayTest extends TestCase {
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 @@ public class JSONArrayTest extends TestCase {
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 @@ public class JSONArrayTest extends TestCase {
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);
@@ -168,7 +168,7 @@ public class JSONArrayTest extends TestCase {
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 +259,32 @@ public class JSONArrayTest extends TestCase {
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 +312,7 @@ public class JSONArrayTest extends TestCase {
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 +371,14 @@ public class JSONArrayTest extends TestCase {
}
}
+ 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 +391,7 @@ public class JSONArrayTest extends TestCase {
}
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 +403,70 @@ public class JSONArrayTest extends TestCase {
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 +503,4 @@ public class JSONArrayTest extends TestCase {
} 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..e431096 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 @@ public class JSONObjectTest extends TestCase {
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 @@ public class JSONObjectTest extends TestCase {
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 class JSONObjectTest extends TestCase {
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 @@ public class JSONObjectTest extends TestCase {
}
};
- // 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 @@ public class JSONObjectTest extends TestCase {
}
};
- // 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());
@@ -527,7 +529,7 @@ public class JSONObjectTest extends TestCase {
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 +592,7 @@ public class JSONObjectTest extends TestCase {
}
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 +609,97 @@ public class JSONObjectTest extends TestCase {
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 +749,7 @@ public class JSONObjectTest extends TestCase {
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 +858,11 @@ public class JSONObjectTest extends TestCase {
}
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 @@ import junit.framework.TestCase;
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 @@ public class JSONStringerTest extends TestCase {
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 class JSONStringerTest extends TestCase {
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 @@ public class JSONStringerTest extends TestCase {
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 @@ public class JSONStringerTest extends TestCase {
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 @@ public class JSONStringerTest extends TestCase {
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 @@ public class JSONStringerTest extends TestCase {
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..70b7384 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 @@ import junit.framework.TestCase;
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 @@ public class JSONTokenerTest extends TestCase {
}
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 @@ public class JSONTokenerTest extends TestCase {
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 @@ public class JSONTokenerTest extends TestCase {
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,24 @@ public class JSONTokenerTest extends TestCase {
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());
+ }
+
+ 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 +274,54 @@ public class JSONTokenerTest extends TestCase {
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());
- }
-
- // 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());
+ 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');
+ }
+
+ 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 +391,7 @@ public class JSONTokenerTest extends TestCase {
try {
new JSONTokener("abc\\u002\"").nextString('"');
fail();
+ } catch (NumberFormatException e) {
} catch (JSONException e) {
}
try {
@@ -433,15 +451,6 @@ public class JSONTokenerTest extends TestCase {
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 +459,32 @@ public class JSONTokenerTest extends TestCase {
}
}
+ 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 +544,6 @@ public class JSONTokenerTest extends TestCase {
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 +557,12 @@ public class JSONTokenerTest extends TestCase {
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 +594,4 @@ public class JSONTokenerTest extends TestCase {
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..16b9116
--- /dev/null
+++ b/json/src/test/java/org/json/ParsingTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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(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 @@ package org.json;
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 @@ public class SelfUseTest extends TestCase {
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 @@ public class SelfUseTest extends TestCase {
}
};
+ 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 @@ public class SelfUseTest extends TestCase {
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);
+ }
}