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 <[email protected]> Authored: Sun May 28 01:40:08 2017 +0200 Committer: Andrea Del Bene <[email protected]> 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); + } }
