You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/08/27 00:24:07 UTC

svn commit: r569893 [2/6] - in /tapestry/tapestry5/trunk: ./ .settings/ quickstart/src/main/resources/archetype-resources/src/main/java/services/ src/site/ src/site/resources/ src/site/xdoc/ support/ tapestry-core/ tapestry-core/src/main/java/org/apach...

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONArray.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONArray.java?rev=569893&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONArray.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONArray.java Sun Aug 26 15:23:55 2007
@@ -0,0 +1,433 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.json;
+
+/*
+ Copyright (c) 2002 JSON.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ The Software shall be used for Good, not Evil.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+
+/**
+ * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in
+ * square brackets with commas separating the values. The internal form is an object having
+ * <code>get</code> and <code>opt</code> methods for accessing the values by index, and
+ * <code>put</code> methods for adding or replacing values. The values can be any of these types:
+ * <code>Boolean</code>, <code>JSONArray</code>, <code>JSONObject</code>,
+ * <code>Number</code>, <code>String</code>, or the <code>JSONObject.NULL object</code>.
+ * <p>
+ * The constructor can convert a JSON text into a Java object. The <code>toString</code> method
+ * converts to JSON text.
+ * <p>
+ * A <code>get</code> method returns a value if one can be found, and throws an exception if one
+ * cannot be found. An <code>opt</code> method returns a default value instead of throwing an
+ * exception, and so is useful for obtaining optional values.
+ * <p>
+ * The generic <code>get()</code> and <code>opt()</code> methods return an object which you can
+ * cast or query for type. There are also typed <code>get</code> and <code>opt</code> methods
+ * that do type checking and type coersion for you.
+ * <p>
+ * The texts produced by the <code>toString</code> methods strictly conform to JSON syntax rules.
+ * The constructors are more forgiving in the texts they will accept:
+ * <ul>
+ * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just before the closing
+ * bracket.</li>
+ * <li>The <code>null</code> value will be inserted when there is <code>,</code>&nbsp;<small>(comma)</small>
+ * elision.</li>
+ * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
+ * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote,
+ * and if they do not contain leading or trailing spaces, and if they do not contain any of these
+ * characters: <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and if they
+ * are not the reserved words <code>true</code>, <code>false</code>, or <code>null</code>.</li>
+ * <li>Values can be separated by <code>;</code> <small>(semicolon)</small> as well as by
+ * <code>,</code> <small>(comma)</small>.</li>
+ * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or <code>0x-</code>
+ * <small>(hex)</small> prefix.</li>
+ * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li>
+ * </ul>
+ * 
+ * @author JSON.org
+ * @version 2
+ */
+public class JSONArray
+{
+
+    /**
+     * The arrayList where the JSONArray's properties are kept.
+     */
+    private final List<Object> _list = CollectionFactory.newList();;
+
+    /**
+     * Construct an empty JSONArray.
+     */
+    public JSONArray()
+    {
+    }
+
+    /**
+     * Construct a JSONArray from a JSONTokener.
+     * 
+     * @param tokenizer
+     *            A JSONTokener
+     * @throws RuntimeException
+     *             If there is a syntax error.
+     */
+    JSONArray(JSONTokener tokenizer)
+    {
+        assert tokenizer != null;
+
+        if (tokenizer.nextClean() != '[') { throw tokenizer
+                .syntaxError("A JSONArray text must start with '['"); }
+
+        if (tokenizer.nextClean() == ']') { return; }
+
+        tokenizer.back();
+
+        while (true)
+        {
+            if (tokenizer.nextClean() == ',')
+            {
+                tokenizer.back();
+                _list.add(null);
+            }
+            else
+            {
+                tokenizer.back();
+                _list.add(tokenizer.nextValue());
+            }
+
+            switch (tokenizer.nextClean())
+            {
+                case ';':
+                case ',':
+                    if (tokenizer.nextClean() == ']') { return; }
+                    tokenizer.back();
+                    break;
+
+                case ']':
+                    return;
+
+                default:
+                    throw tokenizer.syntaxError("Expected a ',' or ']'");
+            }
+        }
+    }
+
+    /**
+     * Get the object value associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return An object value.
+     * @throws RuntimeException
+     *             If there is no value for the index.
+     */
+    public Object get(int index)
+    {
+        return _list.get(index);
+    }
+
+    /**
+     * Get the boolean value associated with an index. The string values "true" and "false" are
+     * converted to boolean.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The truth.
+     * @throws RuntimeException
+     *             If there is no value for the index or if the value is not convertable to boolean.
+     */
+    public boolean getBoolean(int index)
+    {
+        Object value = get(index);
+
+        if (value instanceof Boolean)
+        {
+            Boolean asBoolean = (Boolean) value;
+
+            return asBoolean.booleanValue();
+        }
+
+        if (value instanceof String)
+        {
+            String asString = (String) value;
+
+            if (asString.equalsIgnoreCase("false")) return false;
+
+            if (asString.equalsIgnoreCase("true")) return true;
+        }
+
+        throw new RuntimeException("JSONArray[" + index + "] is not a Boolean.");
+    }
+
+    /**
+     * Get the double value associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted to a number.
+     */
+    public double getDouble(int index)
+    {
+        Object value = get(index);
+
+        try
+        {
+            if (value instanceof Number) return ((Number) value).doubleValue();
+
+            return Double.valueOf((String) value).doubleValue();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("JSONArray[" + index + "] is not a number.");
+        }
+    }
+
+    /**
+     * Get the int value associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted to a number. if the
+     *             value cannot be converted to a number.
+     */
+    public int getInt(int index)
+    {
+        Object o = get(index);
+        return o instanceof Number ? ((Number) o).intValue() : (int) getDouble(index);
+    }
+
+    /**
+     * Get the JSONArray associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A JSONArray value.
+     * @throws RuntimeException
+     *             If there is no value for the index. or if the value is not a JSONArray
+     */
+    public JSONArray getJSONArray(int index)
+    {
+        Object o = get(index);
+        if (o instanceof JSONArray) { return (JSONArray) o; }
+
+        throw new RuntimeException("JSONArray[" + index + "] is not a JSONArray.");
+    }
+
+    /**
+     * Get the JSONObject associated with an index.
+     * 
+     * @param index
+     *            subscript
+     * @return A JSONObject value.
+     * @throws RuntimeException
+     *             If there is no value for the index or if the value is not a JSONObject
+     */
+    public JSONObject getJSONObject(int index)
+    {
+        Object o = get(index);
+        if (o instanceof JSONObject) { return (JSONObject) o; }
+
+        throw new RuntimeException("JSONArray[" + index + "] is not a JSONObject.");
+    }
+
+    /**
+     * Get the long value associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return The value.
+     * @throws JSONException
+     *             If the key is not found or if the value cannot be converted to a number.
+     */
+    public long getLong(int index)
+    {
+        Object o = get(index);
+        return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(index);
+    }
+
+    /**
+     * Get the string associated with an index.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return A string value.
+     * @throws RuntimeException
+     *             If there is no value for the index.
+     */
+    public String getString(int index)
+    {
+        return get(index).toString();
+    }
+
+    /**
+     * Determine if the value is null.
+     * 
+     * @param index
+     *            The index must be between 0 and length() - 1.
+     * @return true if the value at the index is null, or if there is no value.
+     */
+    public boolean isNull(int index)
+    {
+        return get(index) == JSONObject.NULL;
+    }
+
+    /**
+     * Make a string from the contents of this JSONArray. The <code>separator</code> string is
+     * inserted between each element. Warning: This method assumes that the data structure is
+     * acyclical.
+     * 
+     * @param separator
+     *            A string that will be inserted between the elements.
+     * @return a string.
+     * @throws RuntimeException
+     *             If the array contains an invalid number.
+     */
+    public String join(String separator)
+    {
+        int len = length();
+        StringBuilder buffer = new StringBuilder();
+
+        for (int i = 0; i < len; i += 1)
+        {
+            if (i > 0) buffer.append(separator);
+
+            buffer.append(JSONObject.valueToString(_list.get(i)));
+        }
+
+        return buffer.toString();
+    }
+
+    /**
+     * Get the number of elements in the JSONArray, included nulls.
+     * 
+     * @return The length (or size).
+     */
+    public int length()
+    {
+        return _list.size();
+    }
+
+    /**
+     * Append an object value. This increases the array's length by one.
+     * 
+     * @param value
+     *            An object value. The value should be a Boolean, Double, Integer, JSONArray,
+     *            JSONObject, Long, or String, or the JSONObject.NULL object.
+     * @return
+     */
+    public JSONArray put(Object value)
+    {
+        assert value != null;
+
+        JSONObject.testValidity(value);
+
+        _list.add(value);
+
+        return this;
+    }
+
+    /**
+     * Put or replace an object value in the JSONArray. If the index is greater than the length of
+     * the JSONArray, then null elements will be added as necessary to pad it out.
+     * 
+     * @param index
+     *            The subscript.
+     * @param value
+     *            The value to put into the array. The value should be a Boolean, Double, Integer,
+     *            JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
+     * @return
+     * @throws RuntimeException
+     *             If the index is negative or if the the value is an invalid number.
+     */
+    public JSONArray put(int index, Object value)
+    {
+        assert value != null;
+
+        if (index < 0) { throw new RuntimeException("JSONArray[" + index + "] not found."); }
+
+        JSONObject.testValidity(value);
+
+        if (index < length())
+        {
+            _list.set(index, value);
+        }
+        else
+        {
+            while (index != length())
+                _list.add(JSONObject.NULL);
+
+            _list.add(value);
+
+        }
+
+        return this;
+    }
+
+    /**
+     * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If
+     * it is not possible to produce a syntactically correct JSON text then null will be returned
+     * instead. This could occur if the array contains an invalid number.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * 
+     * @return a printable, displayable, transmittable representation of the array.
+     */
+    public String toString()
+    {
+        try
+        {
+            return '[' + join(",") + ']';
+        }
+        catch (Exception e)
+        {
+            return null;
+        }
+    }
+
+    Object[] toArray()
+    {
+        return _list.toArray();
+    }
+}
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONObject.java?rev=569893&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONObject.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONObject.java Sun Aug 26 15:23:55 2007
@@ -0,0 +1,861 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.json;
+
+/*
+ Copyright (c) 2002 JSON.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ The Software shall be used for Good, not Evil.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+
+/**
+ * A JSONObject is an unordered collection of name/value pairs. Its external form is a string
+ * wrapped in curly braces with colons between the names and values, and commas between the values
+ * and names. The internal form is an object having <code>get</code> and <code>opt</code>
+ * methods for accessing the values by name, and <code>put</code> methods for adding or replacing
+ * values by name. The values can be any of these types: <code>Boolean</code>,
+ * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
+ * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject constructor
+ * can be used to convert an external form JSON text into an internal form whose values can be
+ * retrieved with the <code>get</code> and <code>opt</code> methods, or to convert values into a
+ * JSON text using the <code>put</code> and <code>toString</code> methods. A <code>get</code>
+ * method returns a value if one can be found, and throws an exception if one cannot be found. An
+ * <code>opt</code> method returns a default value instead of throwing an exception, and so is
+ * useful for obtaining optional values.
+ * <p>
+ * The generic <code>get()</code> and <code>opt()</code> methods return an object, which you can
+ * cast or query for type. There are also typed <code>get</code> and <code>opt</code> methods
+ * that do type checking and type coersion for you.
+ * <p>
+ * The <code>put</code> methods adds values to an object. For example,
+ * 
+ * <pre>
+ * myString = new JSONObject().put(&quot;JSON&quot;, &quot;Hello, World!&quot;).toString();
+ * </pre>
+ * 
+ * produces the string <code>{"JSON": "Hello, World"}</code>.
+ * <p>
+ * The texts produced by the <code>toString</code> methods strictly conform to the JSON sysntax
+ * rules. The constructors are more forgiving in the texts they will accept:
+ * <ul>
+ * <li>An extra <code>,</code>&nbsp;<small>(comma)</small> may appear just before the closing
+ * brace.</li>
+ * <li>Strings may be quoted with <code>'</code>&nbsp;<small>(single quote)</small>.</li>
+ * <li>Strings do not need to be quoted at all if they do not begin with a quote or single quote,
+ * and if they do not contain leading or trailing spaces, and if they do not contain any of these
+ * characters: <code>{ } [ ] / \ : , = ; #</code> and if they do not look like numbers and if they
+ * are not the reserved words <code>true</code>, <code>false</code>, or <code>null</code>.</li>
+ * <li>Keys can be followed by <code>=</code> or <code>=></code> as well as by <code>:</code>.</li>
+ * <li>Values can be followed by <code>;</code> <small>(semicolon)</small> as well as by
+ * <code>,</code> <small>(comma)</small>.</li>
+ * <li>Numbers may have the <code>0-</code> <small>(octal)</small> or <code>0x-</code>
+ * <small>(hex)</small> prefix.</li>
+ * <li>Comments written in the slashshlash, slashstar, and hash conventions will be ignored.</li>
+ * </ul>
+ * <hr/>
+ * <p>
+ * This class, and the other related classes, have been heavily modified from the original source,
+ * to fit Tapestry standards and to make use of JDK 1.5 features such as generics. Further, since
+ * the interest of Tapestry is primarily constructing JSON (and not parsing it), many of the
+ * non-essential methods have been removed (since the original code came with no tests).
+ * 
+ * @author JSON.org
+ * @version 2
+ */
+public final class JSONObject
+{
+
+    /**
+     * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is
+     * equivalent to the value that JavaScript calls undefined.
+     */
+    private static final class Null
+    {
+
+        /**
+         * There is only intended to be a single instance of the NULL object, so the clone method
+         * returns itself.
+         * 
+         * @return NULL.
+         */
+        protected final Object clone()
+        {
+            return this;
+        }
+
+        /**
+         * A Null object is equal to the null value and to itself.
+         * 
+         * @param object
+         *            An object to test for nullness.
+         * @return true if the object parameter is the JSONObject.NULL object or null.
+         */
+        public boolean equals(Object object)
+        {
+            return object == null || object == this;
+        }
+
+        /**
+         * Get the "null" string value.
+         * 
+         * @return The string "null".
+         */
+        public String toString()
+        {
+            return "null";
+        }
+    }
+
+    /**
+     * The map where the JSONObject's properties are kept.
+     */
+    private final Map<String, Object> _properties = CollectionFactory.newMap();
+
+    /**
+     * It is sometimes more convenient and less ambiguous to have a <code>NULL</code> object than
+     * to use Java's <code>null</code> value. <code>JSONObject.NULL.equals(null)</code> returns
+     * <code>true</code>. <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
+     */
+    public static final Object NULL = new Null();
+
+    /**
+     * Construct an empty JSONObject.
+     */
+    public JSONObject()
+    {
+    }
+
+    /**
+     * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to
+     * identify the keys that should be copied. Missing keys are ignored.
+     * 
+     * @param source
+     *            A JSONObject.
+     * @param propertyNames
+     *            The strings to copy.
+     * @throws RuntimeException
+     *             If a value is a non-finite number.
+     */
+    public JSONObject(JSONObject source, String... propertyNames)
+    {
+        for (String name : propertyNames)
+        {
+            Object value = source.opt(name);
+
+            if (value != null) put(name, value);
+        }
+    }
+
+    /**
+     * Construct a JSONObject from a JSONTokener.
+     * 
+     * @param x
+     * A JSONTokener object containing the source string. @ If there is a syntax error in the source
+     *            string.
+     */
+    JSONObject(JSONTokener x)
+    {
+        String key;
+
+        if (x.nextClean() != '{') { throw x.syntaxError("A JSONObject text must begin with '{'"); }
+
+        while (true)
+        {
+            char c = x.nextClean();
+            switch (c)
+            {
+                case 0:
+                    throw x.syntaxError("A JSONObject text must end with '}'");
+                case '}':
+                    return;
+                default:
+                    x.back();
+                    key = x.nextValue().toString();
+            }
+
+            /*
+             * The key is followed by ':'. We will also tolerate '=' or '=>'.
+             */
+
+            c = x.nextClean();
+            if (c == '=')
+            {
+                if (x.next() != '>')
+                {
+                    x.back();
+                }
+            }
+            else if (c != ':') { throw x.syntaxError("Expected a ':' after a key"); }
+            put(key, x.nextValue());
+
+            /*
+             * Pairs are separated by ','. We will also tolerate ';'.
+             */
+
+            switch (x.nextClean())
+            {
+                case ';':
+                case ',':
+                    if (x.nextClean() == '}') { return; }
+                    x.back();
+                    break;
+                case '}':
+                    return;
+                default:
+                    throw x.syntaxError("Expected a ',' or '}'");
+            }
+        }
+    }
+
+    /**
+     * Construct a JSONObject from a string. This is the most commonly used JSONObject constructor.
+     * 
+     * @param string
+     *            A string beginning with <code>{</code>&nbsp;<small>(left brace)</small> and
+     *            ending with <code>}</code>&nbsp;<small>(right brace)</small>.
+     * @throws RuntimeException
+     *             If there is a syntax error in the source string.
+     */
+    public JSONObject(String string)
+    {
+        this(new JSONTokener(string));
+    }
+
+    /**
+     * Accumulate values under a key. It is similar to the put method except that if there is
+     * already an object stored under the key then a JSONArray is stored under the key to hold all
+     * of the accumulated values. If there is already a JSONArray, then the new value is appended to
+     * it. In contrast, the put method replaces the previous value.
+     * 
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object to be accumulated under the key.
+     * @return this.
+     * @throws {@link RuntimeException}
+     *             If the value is an invalid number or if the key is null.
+     */
+    public JSONObject accumulate(String key, Object value)
+    {
+        testValidity(value);
+
+        Object existing = opt(key);
+
+        if (existing == null)
+        {
+            // Note that the original implementation of this method contradicited the method
+            // documentation.
+            put(key, value);
+            return this;
+        }
+
+        if (existing instanceof JSONArray)
+        {
+            ((JSONArray) existing).put(value);
+            return this;
+        }
+
+        // Replace the existing value, of any type, with an array that includes both the
+        // existing and the new value.
+
+        put(key, new JSONArray().put(existing).put(value));
+
+        return this;
+    }
+
+    /**
+     * Append values to the array under a key. If the key does not exist in the JSONObject, then the
+     * key is put in the JSONObject with its value being a JSONArray containing the value parameter.
+     * If the key was already associated with a JSONArray, then the value parameter is appended to
+     * it.
+     * 
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object to be accumulated under the key.
+     * @return this. @ If the key is null or if the current value associated with the key is not a
+     *         JSONArray.
+     */
+    public JSONObject append(String key, Object value)
+    {
+        testValidity(value);
+        Object o = opt(key);
+        if (o == null)
+        {
+            put(key, new JSONArray().put(value));
+        }
+        else if (o instanceof JSONArray)
+        {
+            put(key, ((JSONArray) o).put(value));
+        }
+        else
+        {
+            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
+        }
+
+        return this;
+    }
+
+    /**
+     * Produce a string from a double. The string "null" will be returned if the number is not
+     * finite.
+     * 
+     * @param d
+     *            A double.
+     * @return A String.
+     */
+    static String doubleToString(double d)
+    {
+        if (Double.isInfinite(d) || Double.isNaN(d)) { return "null"; }
+
+        // Shave off trailing zeros and decimal point, if possible.
+
+        String s = Double.toString(d);
+        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
+        {
+            while (s.endsWith("0"))
+            {
+                s = s.substring(0, s.length() - 1);
+            }
+            if (s.endsWith("."))
+            {
+                s = s.substring(0, s.length() - 1);
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Get the value object associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return The object associated with the key. @ if the key is not found.
+     * @see #opt(String)
+     */
+    public Object get(String key)
+    {
+        Object o = opt(key);
+        if (o == null) { throw new RuntimeException("JSONObject[" + quote(key) + "] not found."); }
+
+        return o;
+    }
+
+    /**
+     * Get the boolean value associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return The truth.
+     * @throws RuntimeException
+     *             if the value is not a Boolean or the String "true" or "false".
+     */
+    public boolean getBoolean(String key)
+    {
+        Object o = get(key);
+
+        if (o instanceof Boolean) return o.equals(Boolean.TRUE);
+
+        if (o instanceof String)
+        {
+            String value = (String) o;
+
+            if (value.equalsIgnoreCase("true")) return true;
+
+            if (value.equalsIgnoreCase("false")) return false;
+        }
+
+        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a Boolean.");
+    }
+
+    /**
+     * Get the double value associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return The numeric value. @ if the key is not found or if the value is not a Number object
+     *         and cannot be converted to a number.
+     */
+    public double getDouble(String key)
+    {
+        Object value = get(key);
+
+        try
+        {
+            if (value instanceof Number) return ((Number) value).doubleValue();
+
+            // This is a bit sloppy for the case where value is not a string.
+
+            return Double.valueOf((String) value).doubleValue();
+        }
+        catch (Exception e)
+        {
+            throw new RuntimeException("JSONObject[" + quote(key) + "] is not a number.");
+        }
+    }
+
+    /**
+     * Get the int value associated with a key. If the number value is too large for an int, it will
+     * be clipped.
+     * 
+     * @param key
+     *            A key string.
+     * @return The integer value. @ if the key is not found or if the value cannot be converted to
+     *         an integer.
+     */
+    public int getInt(String key)
+    {
+        Object value = get(key);
+
+        if (value instanceof Number) return ((Number) value).intValue();
+
+        // Very inefficient way to do this!
+        return (int) getDouble(key);
+    }
+
+    /**
+     * Get the JSONArray value associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return A JSONArray which is the value.
+     * @throws RuntimeException
+     *             if the key is not found or if the value is not a JSONArray.
+     */
+    public JSONArray getJSONArray(String key)
+    {
+        Object o = get(key);
+        if (o instanceof JSONArray) { return (JSONArray) o; }
+
+        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONArray.");
+    }
+
+    /**
+     * Get the JSONObject value associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return A JSONObject which is the value.
+     * @throws RuntimeException
+     *             if the key is not found or if the value is not a JSONObject.
+     */
+    public JSONObject getJSONObject(String key)
+    {
+        Object o = get(key);
+        if (o instanceof JSONObject) { return (JSONObject) o; }
+
+        throw new RuntimeException("JSONObject[" + quote(key) + "] is not a JSONObject.");
+    }
+
+    /**
+     * Get the long value associated with a key. If the number value is too long for a long, it will
+     * be clipped.
+     * 
+     * @param key
+     *            A key string.
+     * @return The long value. @ if the key is not found or if the value cannot be converted to a
+     *         long.
+     */
+    public long getLong(String key)
+    {
+        Object o = get(key);
+        return o instanceof Number ? ((Number) o).longValue() : (long) getDouble(key);
+    }
+
+    /**
+     * Get the string associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return A string which is the value.
+     * @throws RuntimeException
+     *             if the key is not found.
+     */
+    public String getString(String key)
+    {
+        return get(key).toString();
+    }
+
+    /**
+     * Determine if the JSONObject contains a specific key.
+     * 
+     * @param key
+     *            A key string.
+     * @return true if the key exists in the JSONObject.
+     */
+    public boolean has(String key)
+    {
+        return _properties.containsKey(key);
+    }
+
+    /**
+     * Determine if the value associated with the key is null or if there is no value.
+     * 
+     * @param key
+     *            A key string.
+     * @return true if there is no value associated with the key or if the value is the
+     *         JSONObject.NULL object.
+     */
+    public boolean isNull(String key)
+    {
+        return JSONObject.NULL.equals(opt(key));
+    }
+
+    /**
+     * Get an enumeration of the keys of the JSONObject. Caution: the set should not be modified.
+     * 
+     * @return An iterator of the keys.
+     */
+    public Set<String> keys()
+    {
+        return _properties.keySet();
+    }
+
+    /**
+     * Get the number of keys stored in the JSONObject.
+     * 
+     * @return The number of keys in the JSONObject.
+     */
+    public int length()
+    {
+        return _properties.size();
+    }
+
+    /**
+     * Produce a JSONArray containing the names of the elements of this JSONObject.
+     * 
+     * @return A JSONArray containing the key strings, or null if the JSONObject is empty.
+     */
+    public JSONArray names()
+    {
+        JSONArray ja = new JSONArray();
+
+        for (String key : keys())
+        {
+            ja.put(key);
+        }
+
+        return ja.length() == 0 ? null : ja;
+    }
+
+    /**
+     * Produce a string from a Number.
+     * 
+     * @param n
+     *            A Number
+     * @return A String. @ If n is a non-finite number.
+     */
+    static String numberToString(Number n)
+    {
+        assert n != null;
+
+        testValidity(n);
+
+        // Shave off trailing zeros and decimal point, if possible.
+
+        String s = n.toString();
+        if (s.indexOf('.') > 0 && s.indexOf('e') < 0 && s.indexOf('E') < 0)
+        {
+            while (s.endsWith("0"))
+            {
+                s = s.substring(0, s.length() - 1);
+            }
+            if (s.endsWith("."))
+            {
+                s = s.substring(0, s.length() - 1);
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Get an optional value associated with a key.
+     * 
+     * @param key
+     *            A key string.
+     * @return An object which is the value, or null if there is no value.
+     * @see #get(String)
+     */
+    public Object opt(String key)
+    {
+        return _properties.get(key);
+    }
+
+    /**
+     * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed
+     * from the JSONObject if it is present.
+     * 
+     * @param key
+     *            A key string.
+     * @param value
+     *            An object which is the value. It should be of one of these types: Boolean, Double,
+     *            Integer, JSONArray, JSONObject, Long, String, or the JSONObject.NULL object.
+     * @return this.
+     * @throws RuntimeException
+     *             If the value is non-finite number or if the key is null.
+     */
+    public JSONObject put(String key, Object value)
+    {
+        assert key != null;
+
+        if (value != null)
+        {
+            testValidity(value);
+            _properties.put(key, value);
+        }
+        else
+        {
+            remove(key);
+        }
+
+        return this;
+    }
+
+    /**
+     * Produce a string in double quotes with backslash sequences in all the right places. A
+     * backslash will be inserted within </, allowing JSON text to be delivered in HTML. In JSON
+     * text, a string cannot contain a control character or an unescaped quote or backslash.
+     * 
+     * @param string
+     *            A String
+     * @return A String correctly formatted for insertion in a JSON text.
+     */
+    public static String quote(String string)
+    {
+        if (string == null || string.length() == 0) { return "\"\""; }
+
+        char b;
+        char c = 0;
+        int i;
+        int len = string.length();
+        StringBuilder buffer = new StringBuilder(len + 4);
+        String t;
+
+        buffer.append('"');
+        for (i = 0; i < len; i += 1)
+        {
+            b = c;
+            c = string.charAt(i);
+            switch (c)
+            {
+                case '\\':
+                case '"':
+                    buffer.append('\\');
+                    buffer.append(c);
+                    break;
+                case '/':
+                    if (b == '<')
+                    {
+                        buffer.append('\\');
+                    }
+                    buffer.append(c);
+                    break;
+                case '\b':
+                    buffer.append("\\b");
+                    break;
+                case '\t':
+                    buffer.append("\\t");
+                    break;
+                case '\n':
+                    buffer.append("\\n");
+                    break;
+                case '\f':
+                    buffer.append("\\f");
+                    break;
+                case '\r':
+                    buffer.append("\\r");
+                    break;
+                default:
+                    if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
+                            || (c >= '\u2000' && c < '\u2100'))
+                    {
+                        t = "000" + Integer.toHexString(c);
+                        buffer.append("\\u" + t.substring(t.length() - 4));
+                    }
+                    else
+                    {
+                        buffer.append(c);
+                    }
+            }
+        }
+        buffer.append('"');
+        return buffer.toString();
+    }
+
+    /**
+     * Remove a name and its value, if present.
+     * 
+     * @param key
+     *            The name to be removed.
+     * @return The value that was associated with the name, or null if there was no value.
+     */
+    public Object remove(String key)
+    {
+        return _properties.remove(key);
+    }
+
+    private static final Class[] ALLOWED = new Class[]
+    { String.class, Boolean.class, Number.class, JSONObject.class, JSONArray.class, Null.class };
+
+    /**
+     * Throw an exception if the object is an NaN or infinite number, or not a type which may be
+     * stored.
+     * 
+     * @param value
+     * The object to test. @ If o is a non-finite number.
+     */
+    @SuppressWarnings("unchecked")
+    static void testValidity(Object value)
+    {
+        if (value == null) return;
+
+        boolean found = false;
+        Class actual = value.getClass();
+
+        for (Class allowed : ALLOWED)
+        {
+            if (allowed.isAssignableFrom(actual))
+            {
+                found = true;
+                break;
+            }
+        }
+
+        if (!found)
+            throw new RuntimeException(
+                    String
+                            .format(
+                                    "JSONObject properties may be String, Boolean, Number, JSONObject or JSONArray. Type %s is not allowed.",
+                                    actual.getName()));
+
+        if (value instanceof Double)
+        {
+            Double asDouble = (Double) value;
+
+            if (asDouble.isInfinite() || asDouble.isNaN()) { throw new RuntimeException(
+                    "JSON does not allow non-finite numbers."); }
+
+            return;
+        }
+
+        if (value instanceof Float)
+        {
+            Float asFloat = (Float) value;
+
+            if (asFloat.isInfinite() || asFloat.isNaN()) { throw new RuntimeException(
+                    "JSON does not allow non-finite numbers."); }
+
+            return;
+        }
+
+    }
+
+    /**
+     * Make a JSON text of this JSONObject. For compactness, no whitespace is added. If this would
+     * not result in a syntactically correct JSON text, then null will be returned instead.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * 
+     * @return a printable, displayable, portable, transmittable representation of the object,
+     *         beginning with <code>{</code>&nbsp;<small>(left brace)</small> and ending with
+     *         <code>}</code>&nbsp;<small>(right brace)</small>.
+     */
+    public String toString()
+    {
+        boolean comma = false;
+
+        StringBuilder buffer = new StringBuilder("{");
+
+        for (String key : keys())
+        {
+            if (comma) buffer.append(',');
+
+            buffer.append(quote(key.toString()));
+            buffer.append(':');
+            buffer.append(valueToString(_properties.get(key)));
+
+            comma = true;
+        }
+
+        buffer.append('}');
+
+        return buffer.toString();
+    }
+
+    /**
+     * Make a JSON text of an Object value. If the object has an value.toJSONString() method, then
+     * that method will be used to produce the JSON text. The method is required to produce a
+     * strictly conforming text. If the object does not contain a toJSONString method (which is the
+     * most common case), then a text will be produced by the rules.
+     * <p>
+     * Warning: This method assumes that the data structure is acyclical.
+     * 
+     * @param value
+     *            The value to be serialized.
+     * @return a printable, displayable, transmittable representation of the object, beginning with
+     *         <code>{</code>&nbsp;<small>(left brace)</small> and ending with <code>}</code>&nbsp;<small>(right
+     * brace)</small>. @ If the value is or contains an invalid number.
+     */
+    static String valueToString(Object value)
+    {
+        if (value == null || value.equals(null)) { return "null"; }
+
+        if (value instanceof JSONString)
+        {
+            try
+            {
+                String json = ((JSONString) value).toJSONString();
+
+                return quote(json);
+            }
+            catch (Exception e)
+            {
+                throw new RuntimeException(e);
+            }
+
+        }
+
+        if (value instanceof Number) { return numberToString((Number) value); }
+
+        if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { return value
+                .toString(); }
+        return quote(value.toString());
+    }
+}
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONString.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONString.java?rev=569893&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONString.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONString.java Sun Aug 26 15:23:55 2007
@@ -0,0 +1,32 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.json;
+/**
+ * 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 
+ * of using the Object's <code>toString()</code> method and quoting the result.
+ */
+public interface JSONString {
+	/**
+	 * The <code>toJSONString</code> method allows a class to produce its own JSON 
+	 * serialization. 
+	 * 
+	 * @return A strictly syntactically correct JSON text.
+	 */
+	public String toJSONString();
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONTokener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONTokener.java?rev=569893&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONTokener.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/json/JSONTokener.java Sun Aug 26 15:23:55 2007
@@ -0,0 +1,510 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.json;
+
+/*
+ Copyright (c) 2002 JSON.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ The Software shall be used for Good, not Evil.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+/**
+ * A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the
+ * JSONObject and JSONArray constructors to parse JSON source strings.
+ * 
+ * @author JSON.org
+ * @version 2
+ */
+class JSONTokener
+{
+
+    /**
+     * The index of the next character.
+     */
+    private int _index;
+
+    /**
+     * The source string being tokenized.
+     */
+    private final String _source;
+
+    /**
+     * Construct a JSONTokener from a string.
+     * 
+     * @param source
+     *            A source string, in JSON format.
+     */
+    public JSONTokener(String source)
+    {
+        assert source != null;
+
+        _index = 0;
+        _source = source;
+    }
+
+    /**
+     * Back up one character. This provides a sort of lookahead capability, so that you can test for
+     * a digit or letter before attempting to parse the next number or identifier.
+     */
+    public void back()
+    {
+        if (_index > 0)
+        {
+            _index -= 1;
+        }
+    }
+
+    /**
+     * Get the hex value of a character (base16).
+     * 
+     * @param c
+     *            A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'.
+     * @return An int between 0 and 15, or -1 if c was not a hex digit.
+     */
+    static int dehexchar(char c)
+    {
+        if (c >= '0' && c <= '9') { return c - '0'; }
+        if (c >= 'A' && c <= 'F') { return c - ('A' - 10); }
+        if (c >= 'a' && c <= 'f') { return c - ('a' - 10); }
+        return -1;
+    }
+
+    /**
+     * Determine if the source string still contains characters that next() can consume.
+     * 
+     * @return true if not yet at the end of the source.
+     */
+    public boolean more()
+    {
+        return _index < _source.length();
+    }
+
+    /**
+     * Get the next character in the source string.
+     * 
+     * @return The next character, or 0 if past the end of the source string.
+     */
+    public char next()
+    {
+        if (more()) { return _source.charAt(_index++); }
+
+        return 0;
+    }
+
+    /**
+     * Consume the next character, and check that it matches a specified character.
+     * 
+     * @param c
+     *            The character to match.
+     * @return The character.
+     * @throws RuntimeException
+     *             if the character does not match.
+     */
+    public char next(char c)
+    {
+        char n = next();
+        if (n != c) { throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); }
+        return n;
+    }
+
+    /**
+     * Get the next n characters.
+     * 
+     * @param n
+     *            The number of characters to take.
+     * @return A string of n characters.
+     * @throws RuntimeException
+     *             Substring bounds error if there are not n characters remaining in the source
+     *             string.
+     */
+    public String next(int n)
+    {
+        int i = _index;
+        int j = i + n;
+        if (j >= _source.length()) { throw syntaxError("Substring bounds error"); }
+        _index += n;
+        return _source.substring(i, j);
+    }
+
+    /**
+     * Get the next char in the string, skipping whitespace and comments (slashslash, slashstar, and
+     * hash).
+     * 
+     * @throws RuntimeException
+     * @return A character, or 0 if there are no more characters.
+     */
+    public char nextClean()
+    {
+        for (;;)
+        {
+            char c = next();
+            if (c == '/')
+            {
+                switch (next())
+                {
+                    case '/':
+                        do
+                        {
+                            c = next();
+                        }
+                        while (c != '\n' && c != '\r' && c != 0);
+
+                        break;
+                    case '*':
+
+                        while (true)
+                        {
+                            c = next();
+                            if (c == 0) { throw syntaxError("Unclosed comment"); }
+                            if (c == '*')
+                            {
+                                if (next() == '/')
+                                {
+                                    break;
+                                }
+                                back();
+                            }
+                        }
+                        break;
+
+                    default:
+                        back();
+                        return '/';
+                }
+            }
+            else if (c == '#')
+            {
+                do
+                {
+                    c = next();
+                }
+                while (c != '\n' && c != '\r' && c != 0);
+            }
+            else if (c == 0 || c > ' ') { return c; }
+        }
+    }
+
+    /**
+     * Return the characters up to the next close quote character. Backslash processing is done. The
+     * formal JSON format does not allow strings in single quotes, but an implementation is allowed
+     * to accept them.
+     * 
+     * @param quote
+     *            The quoting character, either <code>"</code>&nbsp;<small>(double quote)</small>
+     *            or <code>'</code>&nbsp;<small>(single quote)</small>.
+     * @return A String.
+     * @throws RuntimeException
+     *             Unterminated string.
+     */
+    public String nextString(char quote)
+    {
+        StringBuilder builder = new StringBuilder();
+
+        while (true)
+        {
+            char c = next();
+            switch (c)
+            {
+                case 0:
+                case '\n':
+                case '\r':
+                    throw syntaxError("Unterminated string");
+                case '\\':
+                    c = next();
+                    switch (c)
+                    {
+                        case 'b':
+                            builder.append('\b');
+                            break;
+                        case 't':
+                            builder.append('\t');
+                            break;
+                        case 'n':
+                            builder.append('\n');
+                            break;
+                        case 'f':
+                            builder.append('\f');
+                            break;
+                        case 'r':
+                            builder.append('\r');
+                            break;
+                        case 'u':
+                            builder.append((char) Integer.parseInt(next(4), 16));
+                            break;
+                        case 'x':
+                            builder.append((char) Integer.parseInt(next(2), 16));
+                            break;
+                        default:
+                            builder.append(c);
+                    }
+                    break;
+                default:
+                    if (c == quote) { return builder.toString(); }
+                    builder.append(c);
+            }
+        }
+    }
+
+    /**
+     * Get the text up but not including the specified character or the end of line, whichever comes
+     * first.
+     * 
+     * @param d
+     *            A delimiter character.
+     * @return A string.
+     */
+    public String nextTo(char d)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (;;)
+        {
+            char c = next();
+            if (c == d || c == 0 || c == '\n' || c == '\r')
+            {
+                if (c != 0)
+                {
+                    back();
+                }
+                return sb.toString().trim();
+            }
+            sb.append(c);
+        }
+    }
+
+    /**
+     * Get the text up but not including one of the specified delimeter characters or the end of
+     * line, whichever comes first.
+     * 
+     * @param delimiters
+     *            A set of delimiter characters.
+     * @return A string, trimmed.
+     */
+    public String nextTo(String delimiters)
+    {
+        char c;
+        StringBuffer sb = new StringBuffer();
+        for (;;)
+        {
+            c = next();
+            if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r')
+            {
+                if (c != 0)
+                {
+                    back();
+                }
+                return sb.toString().trim();
+            }
+            sb.append(c);
+        }
+    }
+
+    /**
+     * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long,
+     * or String, or the JSONObject.NULL object.
+     * 
+     * @throws RuntimeException
+     *             If syntax error.
+     * @return An object.
+     */
+    public Object nextValue()
+    {
+        char c = nextClean();
+        String s;
+
+        switch (c)
+        {
+            case '"':
+            case '\'':
+                return nextString(c);
+            case '{':
+                back();
+                return new JSONObject(this);
+            case '[':
+                back();
+                return new JSONArray(this);
+        }
+
+        /*
+         * Handle unquoted text. This could be the values true, false, or null, or it can be a
+         * number. An implementation (such as this one) is allowed to also accept non-standard
+         * forms. Accumulate characters until we reach the end of the text or a formatting
+         * character.
+         */
+
+        StringBuffer sb = new StringBuffer();
+        char b = c;
+        while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0)
+        {
+            sb.append(c);
+            c = next();
+        }
+        back();
+
+        /*
+         * If it is true, false, or null, return the proper value.
+         */
+
+        s = sb.toString().trim();
+        if (s.equals("")) { throw syntaxError("Missing value"); }
+        if (s.equalsIgnoreCase("true")) { return Boolean.TRUE; }
+        if (s.equalsIgnoreCase("false")) { return Boolean.FALSE; }
+        if (s.equalsIgnoreCase("null")) { return JSONObject.NULL; }
+
+        /*
+         * If it might be a number, try converting it. We support the 0- and 0x- conventions. If a
+         * number cannot be produced, then the value will just be a string. Note that the 0-, 0x-,
+         * plus, and implied string conventions are non-standard. A JSON parser is free to accept
+         * non-JSON forms as long as it accepts all correct JSON forms.
+         */
+
+        if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+')
+        {
+            if (b == '0')
+            {
+                if (s.length() > 2 && (s.charAt(1) == 'x' || s.charAt(1) == 'X'))
+                {
+                    try
+                    {
+                        return new Integer(Integer.parseInt(s.substring(2), 16));
+                    }
+                    catch (Exception e)
+                    {
+                        /* Ignore the error */
+                    }
+                }
+                else
+                {
+                    try
+                    {
+                        return new Integer(Integer.parseInt(s, 8));
+                    }
+                    catch (Exception e)
+                    {
+                        /* Ignore the error */
+                    }
+                }
+            }
+            try
+            {
+                return new Integer(s);
+            }
+            catch (Exception e)
+            {
+                try
+                {
+                    return new Long(s);
+                }
+                catch (Exception f)
+                {
+                    try
+                    {
+                        return new Double(s);
+                    }
+                    catch (Exception g)
+                    {
+                        return s;
+                    }
+                }
+            }
+        }
+        return s;
+    }
+
+    /**
+     * Skip characters until the next character is the requested character. If the requested
+     * character is not found, no characters are skipped.
+     * 
+     * @param to
+     *            A character to skip to.
+     * @return The requested character, or zero if the requested character is not found.
+     */
+    public char skipTo(char to)
+    {
+        char c;
+        int index = _index;
+        do
+        {
+            c = next();
+            if (c == 0)
+            {
+                _index = index;
+                return c;
+            }
+        }
+        while (c != to);
+        back();
+        return c;
+    }
+
+    /**
+     * Skip characters until past the requested string. If it is not found, we are left at the end
+     * of the source.
+     * 
+     * @param to
+     *            A string to skip past.
+     */
+    public boolean skipPast(String to)
+    {
+        _index = _source.indexOf(to, _index);
+        if (_index < 0)
+        {
+            _index = _source.length();
+            return false;
+        }
+        _index += to.length();
+        return true;
+
+    }
+
+    /**
+     * Make a JSONException to signal a syntax error.
+     * 
+     * @param message
+     *            The error message.
+     * @return A JSONException object, suitable for throwing
+     */
+    RuntimeException syntaxError(String message)
+    {
+        return new RuntimeException(message + toString());
+    }
+
+    /**
+     * Make a printable string of this JSONTokener.
+     * 
+     * @return " at character [myIndex] of [mySource]"
+     */
+    public String toString()
+    {
+        return " at character " + _index + " of " + _source;
+    }
+}
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/model/ComponentModel.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/model/ComponentModel.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/model/ComponentModel.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/model/ComponentModel.java Sun Aug 26 15:23:55 2007
@@ -16,11 +16,11 @@
 
 import java.util.List;
 
-import org.apache.commons.logging.Log;
 import org.apache.tapestry.annotations.MixinAfter;
 import org.apache.tapestry.annotations.Persist;
 import org.apache.tapestry.annotations.SupportsInformalParameters;
 import org.apache.tapestry.ioc.Resource;
+import org.slf4j.Logger;
 
 /**
  * Defines a component in terms of its capabilities, parameters, sub-components, etc. During
@@ -65,7 +65,7 @@
     String getFieldPersistenceStrategy(String fieldName);
 
     /** Returns object that will be used to log warnings and errors related to this component. */
-    Log getLog();
+    Logger getLogger();
 
     /** Returns a list of the class names of mixins that are part of the component's implementation. */
     List<String> getMixinClassNames();

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/ClassTransformation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/ClassTransformation.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/ClassTransformation.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/ClassTransformation.java Sun Aug 26 15:23:55 2007
@@ -17,8 +17,8 @@
 import java.lang.annotation.Annotation;
 import java.util.List;
 
-import org.apache.commons.logging.Log;
 import org.apache.tapestry.ioc.AnnotationProvider;
+import org.slf4j.Logger;
 
 /**
  * Contains class-specific information used when transforming an raw class into an executable class.
@@ -328,10 +328,10 @@
     Class toClass(String type);
 
     /**
-     * Returns a log, based on the class name being transformed, to which warnings or errors
+     * Returns a logger, based on the class name being transformed, to which warnings or errors
      * concerning the class being transformed may be logged.
      */
-    Log getLog();
+    Logger getLogger();
 
     /** Returns the modifiers for the named field. */
     int getFieldModifiers(String fieldName);

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/Request.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/Request.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/Request.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/Request.java Sun Aug 26 15:23:55 2007
@@ -80,7 +80,7 @@
      * header can't be converted to a date, the method throws an
      * <code>IllegalArgumentException</code>.
      * 
-     * @param name
+     * @param nme
      *            a <code>String</code> specifying the name of the header
      * @return a <code>long</code> value representing the date specified in the header expressed
      *         as the number of milliseconds since January 1, 1970 GMT, or -1 if the named header
@@ -101,4 +101,14 @@
      *            charset used when parsing parameters
      */
     void setEncoding(String requestEncoding);
+    
+    /**
+     * Returns true if the request originated on the client using XmlHttpRequest (the core of any Ajax behavior).
+     * Ajax action requests may behave quite differently than ordinary, page-based requests.  This implementation
+     * currently depends on the client side setting a header: <strong>X-Requested-With=XmlHttpRequest</strong> (this is what
+     * Prototype does).
+     * 
+     * @return true if the request has an XmlHttpRequest origin
+     */
+    boolean isXHR();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestFilter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestFilter.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestFilter.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestFilter.java Sun Aug 26 15:23:55 2007
@@ -19,9 +19,6 @@
 /**
  * Filter interface for {@link org.apache.tapestry.services.RequestHandler}. Implementations of
  * this interface may be contributed into the tapestry.RequestHandler service configuration.
- * 
- * @see TapestryModule#buildRequestHandler(org.apache.commons.logging.Log, java.util.List,
- *      Dispatcher)
  */
 public interface RequestFilter
 {

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestHandler.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestHandler.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestHandler.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/RequestHandler.java Sun Aug 26 15:23:55 2007
@@ -17,11 +17,9 @@
 import java.io.IOException;
 
 /**
- * Service interface for the tapestry.RequestHandler pipeline service.
+ * Service interface for the RequestHandler pipeline service.
  * 
  * @see RequestFilter
- * @see TapestryModule#buildRequestHandler(org.apache.commons.logging.Log, java.util.List,
- *      Dispatcher)
  */
 
 public interface RequestHandler

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry/services/TapestryModule.java Sun Aug 26 15:23:55 2007
@@ -26,7 +26,6 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.logging.Log;
 import org.apache.tapestry.Asset;
 import org.apache.tapestry.Link;
 import org.apache.tapestry.MarkupWriter;
@@ -200,6 +199,7 @@
 import org.apache.tapestry.validator.MinLength;
 import org.apache.tapestry.validator.Regexp;
 import org.apache.tapestry.validator.Required;
+import org.slf4j.Logger;
 
 /**
  * The root module for Tapestry.
@@ -235,7 +235,7 @@
         binder.bind(MarkupWriterFactory.class, MarkupWriterFactoryImpl.class);
     }
 
-    public static Alias build(Log log,
+    public static Alias build(Logger logger,
 
     @Inject
     @Symbol(InternalConstants.TAPESTRY_ALIAS_MODE_SYMBOL)
@@ -246,7 +246,7 @@
 
     Collection<AliasContribution> configuration)
     {
-        AliasManager manager = new AliasManagerImpl(log, configuration);
+        AliasManager manager = new AliasManagerImpl(logger, configuration);
 
         return new AliasImpl(manager, mode, overridesManager);
     }
@@ -304,10 +304,10 @@
      * A companion service to {@linkplain #build(Log, AliasManager, Collection) the Alias service}
      * whose configuration contribution define spot overrides to specific services.
      */
-    public static AliasManager buildAliasOverrides(Log log,
+    public static AliasManager buildAliasOverrides(Logger logger,
             Collection<AliasContribution> configuration)
     {
-        return new AliasManagerImpl(log, configuration);
+        return new AliasManagerImpl(logger, configuration);
     }
 
     /**
@@ -1011,7 +1011,8 @@
     }
 
     /** Initializes the application. */
-    public ApplicationInitializer build(Log log, List<ApplicationInitializerFilter> configuration)
+    public ApplicationInitializer build(Logger logger,
+            List<ApplicationInitializerFilter> configuration)
     {
         ApplicationInitializer terminator = new ApplicationInitializer()
         {
@@ -1022,17 +1023,18 @@
         };
 
         return _pipelineBuilder.build(
-                log,
+                logger,
                 ApplicationInitializer.class,
                 ApplicationInitializerFilter.class,
                 configuration,
                 terminator);
     }
 
-    public HttpServletRequestHandler build(Log log, List<HttpServletRequestFilter> configuration,
+    public HttpServletRequestHandler build(Logger logger,
+            List<HttpServletRequestFilter> configuration,
 
-    @InjectService("RequestHandler")
-    final RequestHandler handler)
+            @InjectService("RequestHandler")
+            final RequestHandler handler)
     {
         HttpServletRequestHandler terminator = new HttpServletRequestHandler()
         {
@@ -1046,14 +1048,14 @@
         };
 
         return _pipelineBuilder.build(
-                log,
+                logger,
                 HttpServletRequestHandler.class,
                 HttpServletRequestFilter.class,
                 configuration,
                 terminator);
     }
 
-    public RequestHandler build(Log log, List<RequestFilter> configuration,
+    public RequestHandler build(Logger logger, List<RequestFilter> configuration,
             @InjectService("MasterDispatcher")
             final Dispatcher masterDispatcher)
     {
@@ -1068,14 +1070,14 @@
         };
 
         return _pipelineBuilder.build(
-                log,
+                logger,
                 RequestHandler.class,
                 RequestFilter.class,
                 configuration,
                 terminator);
     }
 
-    public ServletApplicationInitializer build(Log log,
+    public ServletApplicationInitializer build(Logger logger,
             List<ServletApplicationInitializerFilter> configuration,
             @InjectService("ApplicationInitializer")
             final ApplicationInitializer initializer)
@@ -1093,7 +1095,7 @@
         };
 
         return _pipelineBuilder.build(
-                log,
+                logger,
                 ServletApplicationInitializer.class,
                 ServletApplicationInitializerFilter.class,
                 configuration,
@@ -1182,9 +1184,9 @@
                 ClassFactory.class);
     }
 
-    public ComponentEventResultProcessor buildComponentInstanceResultProcessor(Log log)
+    public ComponentEventResultProcessor buildComponentInstanceResultProcessor(Logger logger)
     {
-        return new ComponentInstanceResultProcessor(_requestPageCache, _linkFactory, log);
+        return new ComponentInstanceResultProcessor(_requestPageCache, _linkFactory, logger);
     }
 
     /**
@@ -1513,10 +1515,10 @@
     }
 
     public PageRenderRequestHandler buildPageRenderRequestHandler(
-            List<PageRenderRequestFilter> configuration, Log log, ServiceResources resources)
+            List<PageRenderRequestFilter> configuration, Logger logger, ServiceResources resources)
     {
         return _pipelineBuilder.build(
-                log,
+                logger,
                 PageRenderRequestHandler.class,
                 PageRenderRequestFilter.class,
                 configuration,
@@ -1524,10 +1526,11 @@
     }
 
     public ComponentActionRequestHandler buildComponentActionRequestHandler(
-            List<ComponentActionRequestFilter> configuration, Log log, ServiceResources resources)
+            List<ComponentActionRequestFilter> configuration, Logger logger,
+            ServiceResources resources)
     {
         return _pipelineBuilder.build(
-                log,
+                logger,
                 ComponentActionRequestHandler.class,
                 ComponentActionRequestFilter.class,
                 configuration,

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng.xml?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng.xml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/conf/testng.xml Sun Aug 26 15:23:55 2007
@@ -27,6 +27,7 @@
       <package name="org.apache.tapestry.integration.pagelevel"/>
       <package name="org.apache.tapestry.corelib.components"/>
       <package name="org.apache.tapestry.dom"/>
+      <package name="org.apache.tapestry.json"/>
       <package name="org.apache.tapestry.services"/>
       <package name="org.apache.tapestry.util"/>
       <package name="org.apache.tapestry.runtime"/>

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/AppModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/AppModule.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/AppModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/AppModule.java Sun Aug 26 15:23:55 2007
@@ -18,7 +18,6 @@
 import java.net.URL;
 import java.util.List;
 
-import org.apache.commons.logging.Log;
 import org.apache.tapestry.integration.app1.data.Track;
 import org.apache.tapestry.ioc.MappedConfiguration;
 import org.apache.tapestry.ioc.OrderedConfiguration;
@@ -27,13 +26,14 @@
 import org.apache.tapestry.services.RequestFilter;
 import org.apache.tapestry.services.RequestHandler;
 import org.apache.tapestry.services.Response;
+import org.slf4j.Logger;
 
 /**
  * I was just dying to see how fast requests are!
  */
 public class AppModule
 {
-    public RequestFilter buildTimingFilter(final Log log)
+    public RequestFilter buildTimingFilter(final Logger log)
     {
         return new RequestFilter()
         {
@@ -91,7 +91,7 @@
         return new ToDoDatabaseImpl();
     }
 
-    public MusicLibrary buildMusicLibrary(Log log)
+    public MusicLibrary buildMusicLibrary(Logger log)
     {
         URL library = getClass().getResource("iTunes.xml");
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/MusicLibraryParser.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/MusicLibraryParser.java?rev=569893&r1=569892&r2=569893&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/MusicLibraryParser.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry/integration/app1/services/MusicLibraryParser.java Sun Aug 26 15:23:55 2007
@@ -23,9 +23,9 @@
 import java.net.URL;
 import java.util.List;
 
-import org.apache.commons.logging.Log;
 import org.apache.tapestry.integration.app1.data.Track;
 import org.apache.tapestry.ioc.util.Stack;
+import org.slf4j.Logger;
 import org.xml.sax.Attributes;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -39,7 +39,7 @@
  */
 public class MusicLibraryParser
 {
-    private final Log _log;
+    private final Logger _logger;
 
     private static final int STATE_START = 0;
 
@@ -67,11 +67,9 @@
 
         void addContent(char buffer[], int start, int length)
         {
-            if (_ignoreCharacterData)
-                return;
+            if (_ignoreCharacterData) return;
 
-            if (_buffer == null)
-                _buffer = new StringBuilder(length);
+            if (_buffer == null) _buffer = new StringBuilder(length);
 
             _buffer.append(buffer, start, length);
         }
@@ -351,14 +349,14 @@
         }
     }
 
-    public MusicLibraryParser(final Log log)
+    public MusicLibraryParser(final Logger logger)
     {
-        _log = log;
+        _logger = logger;
     }
 
     public List<Track> parseTracks(URL resource)
     {
-        _log.info(format("Parsing music library %s", resource));
+        _logger.info(format("Parsing music library %s", resource));
 
         long start = System.currentTimeMillis();
 
@@ -383,7 +381,7 @@
         List<Track> result = handler.getTracks();
         long elapsed = System.currentTimeMillis() - start;
 
-        _log.info(format("Parsed %d tracks in %d ms", result.size(), elapsed));
+        _logger.info(format("Parsed %d tracks in %d ms", result.size(), elapsed));
 
         return result;
     }