You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by ad...@apache.org on 2017/05/27 23:40:05 UTC
wicket git commit: porting of the new JSON classes from 7.x to 6.x
Repository: wicket
Updated Branches:
refs/heads/wicket-6.x 733bc3953 -> c93725ea9
porting of the new JSON classes from 7.x to 6.x
Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/c93725ea
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/c93725ea
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/c93725ea
Branch: refs/heads/wicket-6.x
Commit: c93725ea9d7cb8210fa058b068382ec1bd42d4c9
Parents: 733bc39
Author: Andrea Del Bene <ad...@apache.org>
Authored: Sun May 28 01:40:08 2017 +0200
Committer: Andrea Del Bene <ad...@apache.org>
Committed: Sun May 28 01:40:08 2017 +0200
----------------------------------------------------------------------
.../java/org/apache/wicket/ajax/json/JSON.java | 227 +--
.../org/apache/wicket/ajax/json/JSONArray.java | 30 +-
.../apache/wicket/ajax/json/JSONException.java | 24 +-
.../org/apache/wicket/ajax/json/JSONObject.java | 114 +-
.../org/apache/wicket/ajax/json/JSONString.java | 18 +-
.../apache/wicket/ajax/json/JSONStringer.java | 49 +-
.../apache/wicket/ajax/json/JSONTokener.java | 1365 ++++++++++--------
7 files changed, 1022 insertions(+), 805 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSON.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSON.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSON.java
index c6161c4..679c128 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSON.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSON.java
@@ -16,101 +16,148 @@
package org.apache.wicket.ajax.json;
-class JSON {
- /**
- * Returns the input if it is a JSON-permissible 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;
- }
+class JSON
+{
+ /**
+ * Returns the input if it is a JSON-permissible 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) {
- String stringValue = (String) value;
- if ("true".equalsIgnoreCase(stringValue)) {
- return true;
- } else if ("false".equalsIgnoreCase(stringValue)) {
- return false;
- }
- }
- return null;
- }
+ static Boolean toBoolean(Object value)
+ {
+ if (value instanceof Boolean)
+ {
+ return (Boolean)value;
+ }
+ else if (value instanceof String)
+ {
+ String stringValue = (String)value;
+ if ("true".equalsIgnoreCase(stringValue))
+ {
+ return true;
+ }
+ else if ("false".equalsIgnoreCase(stringValue))
+ {
+ return false;
+ }
+ }
+ 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 ignored) {
- }
- }
- 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 ignored)
+ {
+ }
+ }
+ 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 ignored) {
- }
- }
- 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 ignored)
+ {
+ }
+ }
+ 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 ignored) {
- }
- }
- 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 ignored)
+ {
+ }
+ }
+ return null;
+ }
- static String toString(Object value) {
- if (value instanceof String) {
- return (String) value;
- } else if (value != null) {
- return String.valueOf(value);
- }
- 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 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);
- }
- }
+ 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);
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONArray.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONArray.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONArray.java
index 081686d..17b69bf 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONArray.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONArray.java
@@ -24,6 +24,8 @@ import java.util.List;
import java.util.Map;
import org.apache.wicket.WicketRuntimeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// Note: this class was written without inspecting the non-free org.json sourcecode.
@@ -50,7 +52,8 @@ import org.apache.wicket.WicketRuntimeException;
* prohibit it" for further information.
*/
public class JSONArray {
-
+ private final static Logger log = LoggerFactory.getLogger(JSONArray.class);
+
private final List<Object> values;
/**
@@ -714,11 +717,10 @@ public class JSONArray {
@Override
public String toString() {
try {
- JSONStringer stringer = new JSONStringer();
- writeTo(stringer);
- return stringer.toString();
+ return toString(new JSONStringer());
} catch (JSONException e) {
- return null;
+ log.error("Unexpected exception", e);
+ return null;
}
}
@@ -737,17 +739,23 @@ public class JSONArray {
* @throws JSONException Only if there is a coding error.
*/
public String toString(int indentSpaces) throws JSONException {
- JSONStringer stringer = new JSONStringer(indentSpaces);
- writeTo(stringer);
- return stringer.toString();
+ return toString(new JSONStringer(indentSpaces));
}
- void writeTo(JSONStringer stringer) throws JSONException {
+ /**
+ * Encodes this array using {@link JSONStringer} provided
+ *
+ * @param stringer - {@link JSONStringer} to be used for serialization
+ * @return The string representation of this.
+ * @throws JSONException On internal errors. Shouldn't happen.
+ */
+ public String toString(JSONStringer stringer) throws JSONException {
stringer.array();
for (Object value : values) {
stringer.value(value);
}
stringer.endArray();
+ return stringer.toString();
}
@Override
@@ -767,11 +775,11 @@ public class JSONArray {
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
}
- public JSONArray put(Map map){
+ public JSONArray put(Map<?, ?> map){
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
}
- public JSONArray put(int integer, Map map){
+ public JSONArray put(int integer, Map<?, ?> map){
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
}
}
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONException.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONException.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONException.java
index d6530a3..f68ce39 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONException.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONException.java
@@ -41,17 +41,21 @@ package org.apache.wicket.ajax.json;
* }
* }</pre>
*/
-public class JSONException extends RuntimeException {
+public class JSONException extends RuntimeException
+{
- public JSONException(String s) {
- super(s);
- }
+ public JSONException(String s)
+ {
+ super(s);
+ }
- public JSONException(Throwable cause) {
- super(cause);
- }
+ public JSONException(Throwable cause)
+ {
+ super(cause);
+ }
- public JSONException(String message, Throwable cause) {
- super(message, cause);
- }
+ public JSONException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
}
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONObject.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONObject.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONObject.java
index b67c25e..8a9d0d1 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONObject.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONObject.java
@@ -31,6 +31,8 @@ import java.util.Set;
import java.util.TreeMap;
import org.apache.wicket.WicketRuntimeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// Note: this class was written without inspecting the non-free org.json sourcecode.
@@ -87,7 +89,8 @@ import org.apache.wicket.WicketRuntimeException;
* prohibit it" for further information.
*/
public class JSONObject {
-
+ private final static Logger log = LoggerFactory.getLogger(JSONObject.class);
+
private static final Double NEGATIVE_ZERO = -0d;
/**
@@ -115,7 +118,6 @@ public class JSONObject {
// at least make the broken equals(null) consistent with Objects.hashCode(null).
@Override
public int hashCode() {
- // replaced Objects.hashCode(null) with 0, because of Java 6 and backward compatibility
return 0;
}
@@ -145,17 +147,19 @@ public class JSONObject {
/* (accept a raw type for API compatibility) */
public JSONObject(Map copyFrom) {
this();
- Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
- for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
+ if (copyFrom != null) {
+ Map<?, ?> contentsTyped = 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("key == null");
+ String key = (String) entry.getKey();
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ nameValuePairs.put(key, wrap(entry.getValue()));
}
- nameValuePairs.put(key, wrap(entry.getValue()));
}
}
@@ -218,29 +222,38 @@ public class JSONObject {
* @throws JSONException If there is an exception while reading the bean
*/
public JSONObject(Object bean) throws JSONException {
- this(propertiesAsMap(bean));
- }
-
- private static Map<String, Object> propertiesAsMap(Object bean)
- throws JSONException {
- Map<String, Object> props = new TreeMap<String, Object>();
- try{
- PropertyDescriptor[] properties = Introspector.getBeanInfo(bean.getClass(), Object.class)
- .getPropertyDescriptors();
- for (int i = 0; i < properties.length; i++) {
- PropertyDescriptor propertyDescriptor = properties[i];
- props.put(propertyDescriptor.getDisplayName(), propertyDescriptor.getReadMethod().invoke(bean));
- }
- }catch( IllegalAccessException e){
- throw new JSONException(e);
- }catch(IntrospectionException e){
- throw new JSONException(e);
- }catch(InvocationTargetException e){
- throw new JSONException(e);
- }
+ this(bean instanceof JSONObject ? ((JSONObject)bean).nameValuePairs : propertiesAsMap(bean));
+ }
+
+ private static Map<String, Object> propertiesAsMap(Object bean) throws JSONException {
+ Map<String, Object> props = new TreeMap<String, Object>();
+ try {
+ PropertyDescriptor[] properties = Introspector.getBeanInfo(bean.getClass(), Object.class)
+ .getPropertyDescriptors();
+ for (PropertyDescriptor prop : properties) {
+ Object v = prop.getReadMethod().invoke(bean);
+ props.put(prop.getDisplayName(), wrap(v));
+ }
+ } catch (IllegalAccessException e) {
+ throw new JSONException(e);
+ } catch (IntrospectionException e) {
+ throw new JSONException(e);
+ } catch (InvocationTargetException e) {
+ throw new JSONException(e);
+ }
return props;
}
+ public static String[] getNames(JSONObject x) {
+ Set<String> names = x.keySet();
+ String[] r = new String[names.size()];
+ int i = 0;
+ for (String name : names) {
+ r[i++] = name;
+ }
+ return r;
+ }
+
/**
* Returns the number of name/value mappings in this object.
*
@@ -317,6 +330,10 @@ public class JSONObject {
* Integer, Long, Double, {@link #NULL}, or {@code null}. May not be
* {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
* infinities}.
+ * If value is Map or Collection the value is wrapped using
+ * corresponding JSONObject(map) or JSONArray(collection) object.
+ * This behavior is considered unsafe and is added for compatibility
+ * with original 'org.json' package only.
* @return this object.
* @throws JSONException if the value is an invalid double (infinite or NaN).
*/
@@ -325,11 +342,16 @@ public class JSONObject {
nameValuePairs.remove(name);
return this;
}
+ Object valueToPut = value;
if (value instanceof Number) {
// deviate from the original by checking all Numbers, not just floats & doubles
JSON.checkDouble(((Number) value).doubleValue());
+ } else if (value instanceof Collection) {
+ valueToPut = new JSONArray((Collection) value);
+ } else if (value instanceof Map) {
+ valueToPut = new JSONObject((Map)value);
}
- nameValuePairs.put(checkName(name), value);
+ nameValuePairs.put(checkName(name), valueToPut);
return this;
}
@@ -840,10 +862,9 @@ public class JSONObject {
@Override
public String toString() {
try {
- JSONStringer stringer = new JSONStringer();
- writeTo(stringer);
- return stringer.toString();
+ return toString(new JSONStringer());
} catch (JSONException e) {
+ log.error("Unexpected exception", e);
return null;
}
}
@@ -866,17 +887,23 @@ public class JSONObject {
* @throws JSONException On internal errors. Shouldn't happen.
*/
public String toString(int indentSpaces) throws JSONException {
- JSONStringer stringer = new JSONStringer(indentSpaces);
- writeTo(stringer);
- return stringer.toString();
+ return toString(new JSONStringer(indentSpaces));
}
- void writeTo(JSONStringer stringer) throws JSONException {
+ /**
+ * Encodes this object using {@link JSONStringer} provided
+ *
+ * @param stringer - {@link JSONStringer} to be used for serialization
+ * @return The string representation of this.
+ * @throws JSONException On internal errors. Shouldn't happen.
+ */
+ public String toString(JSONStringer stringer) throws JSONException {
stringer.object();
for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
- stringer.key(entry.getKey()).value(entry.getValue());
+ stringer.entry(entry);
}
stringer.endObject();
+ return stringer.toString();
}
/**
@@ -901,7 +928,7 @@ public class JSONObject {
}
long longValue = number.longValue();
- if (doubleValue == (double) longValue) {
+ if (doubleValue == longValue) {
return Long.toString(longValue);
}
@@ -940,7 +967,8 @@ public class JSONObject {
* If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
* If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
* If the object is a primitive wrapper type or {@code String}, returns the object.
- * Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
+ * If the object is from a {@code java} package, returns the result of {@code toString}.
+ * If the object is some other kind of object then it is assumed to be a bean and is converted to a JSONObject.
* If wrapping fails, returns null.
*
* @param o The object to wrap.
@@ -977,7 +1005,7 @@ public class JSONObject {
o instanceof JSONString) { // adding JSONString to list for compatibility with org.json
return o;
}
- if (o.getClass().getPackage().getName().startsWith("java.")) {
+ if (o.getClass().getPackage().getName().startsWith("java.") || o instanceof Enum<?>) {
return o.toString();
} else {
return new JSONObject(o);
@@ -987,7 +1015,7 @@ public class JSONObject {
return null;
}
- // Methods removed due to switch to open-json
+// Methods removed due to switch to open-json
public Writer write(Writer writer){
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
@@ -1029,10 +1057,6 @@ public class JSONObject {
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
}
- public String[] getNames(JSONObject jsonObject){
- throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
- }
-
public String doubleToString(double dou){
throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
}
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONString.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONString.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONString.java
index 00f24d6..6787c86 100755
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONString.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONString.java
@@ -1,18 +1,18 @@
package org.apache.wicket.ajax.json;
-
/**
- * The <code>JSONString</code> interface allows a <code>toJSONString()</code>
- * method so that a class can change the behavior of
+ * The <code>JSONString</code> interface allows a <code>toJSONString()</code>
+ * method so that a class can change the behavior of
* <code>JSONObject.toString()</code>, <code>JSONArray.toString()</code>,
- * and <code>JSONWriter.value(</code>Object<code>)</code>. The
- * <code>toJSONString</code> method will be used instead of the default behavior
+ * and <code>JSONWriter.value(</code>Object<code>)</code>. The
+ * <code>toJSONString</code> method will be used instead of the default behavior
* of using the Object's <code>toString()</code> method and quoting the result.
*/
-public interface JSONString {
+public interface JSONString
+{
/**
- * The <code>toJSONString</code> method allows a class to produce its own JSON
- * serialization.
- *
+ * The <code>toJSONString</code> method allows a class to produce its own JSON
+ * serialization.
+ *
* @return A strictly syntactically correct JSON text.
*/
public String toJSONString();
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONStringer.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONStringer.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONStringer.java
index 5085995..14d96ac 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONStringer.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONStringer.java
@@ -19,6 +19,7 @@ package org.apache.wicket.ajax.json;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
// Note: this class was written without inspecting the non-free org.json sourcecode.
@@ -58,12 +59,12 @@ import java.util.List;
* Item 17, "Design and Document or inheritance or else prohibit it" for further
* information.
*/
-public class JSONStringer extends JSONWriter{
+public class JSONStringer extends JSONWriter {
/**
* The output data, containing at most one top-level array or object.
*/
- final StringBuilder out = new StringBuilder();
+ final protected StringBuilder out = new StringBuilder();
/**
* Lexical scoping elements within this stringer, necessary to insert the
@@ -237,19 +238,24 @@ public class JSONStringer extends JSONWriter{
}
if (value instanceof JSONArray) {
- ((JSONArray) value).writeTo(this);
+ ((JSONArray) value).toString(this);
return this;
} else if (value instanceof JSONObject) {
- ((JSONObject) value).writeTo(this);
+ ((JSONObject) value).toString(this);
return this;
}
beforeValue();
+ if (value instanceof JSONString) {
+ out.append(((JSONString) value).toJSONString());
+ return this;
+ }
+
if (value == null
- || value instanceof Boolean
- || value == JSONObject.NULL) {
+ || value instanceof Boolean
+ || value == JSONObject.NULL) {
out.append(value);
} else if (value instanceof Number) {
@@ -258,7 +264,7 @@ public class JSONStringer extends JSONWriter{
} else {
// Hack to make it possible that the value is not surrounded by quotes. (Used for JavaScript function calls)
// Example: { "name": "testkey", "value": window.myfunction() }
- if (value.getClass().getSimpleName().contains("JsonFunction")) {
+ if (value.getClass().getSimpleName().contains("JSONFunction")) {
// note that no escaping of quotes (or anything else) is done in this case.
// that is fine because the only way to get to this point is to
// explicitly put a special kind of object into the JSON data structure.
@@ -320,6 +326,20 @@ public class JSONStringer extends JSONWriter{
return this;
}
+ /**
+ * Encodes {@code key}/{@code value} pair to this stringer.
+ *
+ * @param entry The entry to encode.
+ * @return this stringer.
+ * @throws JSONException If we have an internal error. Shouldn't happen.
+ */
+ public JSONStringer entry(Map.Entry<String, Object> entry) {
+ if (!JSONObject.NULL.equals(entry.getValue())) {
+ this.key(entry.getKey()).value(entry.getValue());
+ }
+ return this;
+ }
+
private void string(String value) {
out.append("\"");
char currentChar = 0;
@@ -393,6 +413,18 @@ public class JSONStringer extends JSONWriter{
}
/**
+ * Creates String representation of the key (property name) to this stringer
+ * Override this method to provide your own representation of the name.
+ *
+ * @param name the name of the forthcoming value.
+ * @return this stringer.
+ */
+ protected JSONStringer createKey(String name) {
+ string(name);
+ return this;
+ }
+
+ /**
* Encodes the key (property name) to this stringer.
*
* @param name the name of the forthcoming value. May not be null.
@@ -404,8 +436,7 @@ public class JSONStringer extends JSONWriter{
throw new JSONException("Names must be non-null");
}
beforeKey();
- string(name);
- return this;
+ return createKey(name);
}
/**
http://git-wip-us.apache.org/repos/asf/wicket/blob/c93725ea/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONTokener.java
----------------------------------------------------------------------
diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONTokener.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONTokener.java
index bc3e68a..d2ebc0d 100644
--- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONTokener.java
+++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONTokener.java
@@ -26,649 +26,752 @@ import java.util.Collection;
import org.apache.wicket.WicketRuntimeException;
/**
- * 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 ] "
- * + "}";
+ * 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();
+ * JSONObject object = (JSONObject)new JSONTokener(json).nextValue();
* String query = object.getString("query");
- * JSONArray locations = object.getJSONArray("locations");</pre>
+ * 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:
+ * <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>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 interpreted as if null was the
- * omitted value.
+ * <li>Unnecessary array separators. These are interpreted 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 overrideable methods is not specified. See <i>Effective Java</i>
- * Item 17, "Design and Document or inheritance or else prohibit it" for further
+ * <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 overrideable 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 to be returned by {@link #next}. When
- * the input is exhausted, this equals the input's length.
- */
- private int pos;
-
- /**
- * @param in JSON encoded string. Null is not permitted and will yield a
- * tokener that throws {@code NullPointerExceptions} when methods are
- * called.
- */
- public JSONTokener(String in) {
- // consume an optional byte order mark (BOM) if it exists
- if (in != null && in.startsWith("\ufeff")) {
- in = in.substring(1);
- }
- this.in = in;
- }
-
- public JSONTokener(Reader input) throws IOException {
- StringBuilder s = new StringBuilder();
- char[] readBuf = new char[102400];
- int n = input.read(readBuf);
- while (n >= 0) {
- s.append(readBuf, 0, n);
- n = input.read(readBuf);
- }
- in = s.toString();
- pos = 0;
- }
-
- /**
- * Returns the next value from the input.
- *
- * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean,
- * Integer, Long, Double or {@link JSONObject#NULL}.
- * @throws JSONException if the input is malformed.
- */
- 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);
- 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 behavior, but it's required to parse
- * existing documents. See http://b/2571423.
- */
- skipToEndOfLine();
- continue;
-
- default:
- return c;
- }
- }
-
- return -1;
- }
-
- /**
- * 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.
- */
- private void skipToEndOfLine() {
- for (; pos < in.length(); pos++) {
- char c = in.charAt(pos);
- if (c == '\r' || c == '\n') {
- pos++;
- break;
- }
- }
- }
-
- /**
- * 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 ".
- * @return The unescaped string.
- * @throws JSONException if the string isn't terminated by a closing quote correctly.
- */
- 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
- //noinspection RedundantStringConstructorCall
- 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".
- */
- 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;
- try {
- return (char) Integer.parseInt(hex, 16);
- } catch (NumberFormatException nfe) {
- throw syntaxError("Invalid escape sequence: " + hex);
- }
-
- 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 ignored) {
- }
-
- /* ... finally give up. We have an unquoted string */
- //noinspection RedundantStringConstructorCall
- 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());
- }
- }
-
- /*
- * 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((Collection<?>)null);
- }
- return result;
- case ',':
- case ';':
- /* A separator without a value first means "null". */
- result.put((Collection<?>)null);
- hasTrailingSeparator = true;
- continue;
- default:
- pos--;
- }
-
- result.put(nextValue());
-
- switch (nextCleanInternal()) {
- case ']':
- return result;
- case ',':
- case ';':
- hasTrailingSeparator = true;
- continue;
- default:
- throw syntaxError("Unterminated array");
- }
- }
- }
-
- /**
- * Returns an exception containing the given message plus the current
- * position and the entire input string.
- *
- * @param message The message we want to include.
- * @return An exception that we can throw.
- */
- public JSONException syntaxError(String message) {
- 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.
- */
-
- /**
- * Returns true until the input has been exhausted.
- *
- * @return true if more input exists.
- */
- 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'.
- *
- * @return the next character.
- */
- 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.
- *
- * @param c The character we are looking for.
- * @return the next character.
- * @throws JSONException If the next character isn't {@code c}
- */
- 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'.
- *
- * @return The next non-whitespace character.
- * @throws JSONException Should not be possible.
- */
- 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.
- *
- * @param length The desired number of characters to return.
- * @return The next few characters.
- * @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.
- *
- * @param excluded The limiting string where the search should stop.
- * @return a possibly-empty string
- */
- public String nextTo(String excluded) {
- if (excluded == null) {
- throw new NullPointerException("excluded == null");
- }
- return nextToInternal(excluded).trim();
- }
-
- /**
- * Equivalent to {@code nextTo(String.valueOf(excluded))}.
- *
- * @param excluded The limiting character.
- * @return a possibly-empty string
- */
- 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.
- *
- * @param thru The string to skip over.
- * @return boolean
- */
- public boolean skipPast(String thru) {
- int thruStart = in.indexOf(thru, pos);
- pos = thruStart == -1 ? in.length() : (thruStart + thru.length());
- return true;
- }
-
- /**
- * 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.
- *
- * @param to The character we want to skip to.
- * @return The value of {@code to} or null.
- */
- 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.
- * @return The decoded integer.
- */
- 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;
- }
- }
-
- // Methods removed due to switch to open-json
-
- public boolean end(){
- throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
- }
-
- public JSONTokener(InputStream inputStream){
- throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
- }
+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;
+
+ /**
+ * @param in
+ * JSON encoded string. Null is not permitted and will yield a tokener that throws
+ * {@code NullPointerExceptions} when methods are called.
+ */
+ public JSONTokener(String in)
+ {
+ // consume an optional byte order mark (BOM) if it exists
+ if (in != null && in.startsWith("\ufeff"))
+ {
+ in = in.substring(1);
+ }
+ this.in = in;
+ }
+
+ public JSONTokener(Reader input) throws IOException
+ {
+ StringBuilder s = new StringBuilder();
+ char[] readBuf = new char[102400];
+ int n = input.read(readBuf);
+ while (n >= 0)
+ {
+ s.append(readBuf, 0, n);
+ n = input.read(readBuf);
+ }
+ in = s.toString();
+ pos = 0;
+ }
+
+ /**
+ * Returns the next value from the input.
+ *
+ * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, Double or
+ * {@link JSONObject#NULL}.
+ * @throws JSONException
+ * if the input is malformed.
+ */
+ 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);
+ 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
+ * behavior, but it's required to parse existing documents. See
+ * http://b/2571423.
+ */
+ skipToEndOfLine();
+ continue;
+
+ default :
+ return c;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * 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.
+ */
+ private void skipToEndOfLine()
+ {
+ for (; pos < in.length(); pos++)
+ {
+ char c = in.charAt(pos);
+ if (c == '\r' || c == '\n')
+ {
+ pos++;
+ break;
+ }
+ }
+ }
+
+ /**
+ * 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 ".
+ * @return The unescaped string.
+ * @throws JSONException
+ * if the string isn't terminated by a closing quote correctly.
+ */
+ 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
+ // noinspection RedundantStringConstructorCall
+ 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".
+ */
+ 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;
+ try
+ {
+ return (char)Integer.parseInt(hex, 16);
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw syntaxError("Invalid escape sequence: " + hex);
+ }
+
+ 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 ignored)
+ {
+ }
+
+ /* ... finally give up. We have an unquoted string */
+ // noinspection RedundantStringConstructorCall
+ 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());
+ }
+ }
+
+ /*
+ * 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((Collection<?>)null);
+ }
+ return result;
+ case ',' :
+ case ';' :
+ /* A separator without a value first means "null". */
+ result.put((Collection<?>)null);
+ hasTrailingSeparator = true;
+ continue;
+ default :
+ pos--;
+ }
+
+ result.put(nextValue());
+
+ switch (nextCleanInternal())
+ {
+ case ']' :
+ return result;
+ case ',' :
+ case ';' :
+ hasTrailingSeparator = true;
+ continue;
+ default :
+ throw syntaxError("Unterminated array");
+ }
+ }
+ }
+
+ /**
+ * Returns an exception containing the given message plus the current position and the entire
+ * input string.
+ *
+ * @param message
+ * The message we want to include.
+ * @return An exception that we can throw.
+ */
+ public JSONException syntaxError(String message)
+ {
+ 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.
+ */
+
+ /**
+ * Returns true until the input has been exhausted.
+ *
+ * @return true if more input exists.
+ */
+ 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'.
+ *
+ * @return the next character.
+ */
+ 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.
+ *
+ * @param c
+ * The character we are looking for.
+ * @return the next character.
+ * @throws JSONException
+ * If the next character isn't {@code c}
+ */
+ 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'.
+ *
+ * @return The next non-whitespace character.
+ * @throws JSONException
+ * Should not be possible.
+ */
+ 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.
+ *
+ * @param length
+ * The desired number of characters to return.
+ * @return The next few characters.
+ * @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.
+ *
+ * @param excluded
+ * The limiting string where the search should stop.
+ * @return a possibly-empty string
+ */
+ public String nextTo(String excluded)
+ {
+ if (excluded == null)
+ {
+ throw new NullPointerException("excluded == null");
+ }
+ return nextToInternal(excluded).trim();
+ }
+
+ /**
+ * Equivalent to {@code nextTo(String.valueOf(excluded))}.
+ *
+ * @param excluded
+ * The limiting character.
+ * @return a possibly-empty string
+ */
+ 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.
+ *
+ * @param thru
+ * The string to skip over.
+ * @return
+ */
+ public boolean skipPast(String thru)
+ {
+ int thruStart = in.indexOf(thru, pos);
+ pos = thruStart == -1 ? in.length() : (thruStart + thru.length());
+ return true;
+ }
+
+ /**
+ * 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.
+ *
+ * @param to
+ * The character we want to skip to.
+ * @return The value of {@code to} or null.
+ */
+ 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.
+ * @return The decoded integer.
+ */
+ 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;
+ }
+ }
+
+ // Methods removed due to switch to open-json
+
+ public boolean end()
+ {
+ throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
+ }
+
+ public JSONTokener(InputStream inputStream)
+ {
+ throw new WicketRuntimeException(JsonConstants.OPEN_JSON_EXCEPTION);
+ }
}