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);
+       }
 }

Reply via email to