http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/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 old mode 100755 new mode 100644 index 76b2b4e..5aa8f5a --- 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 @@ -1,79 +1,470 @@ -package org.apache.wicket.ajax.json; - -/* -Copyright (c) 2006 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.StringWriter; - -/** - * JSONStringer provides a quick and convenient way of producing JSON text. - * The texts produced strictly conform to JSON syntax rules. No whitespace is - * added, so the results are ready for transmission or storage. Each instance of - * JSONStringer can produce one JSON text. - * <p> - * A JSONStringer instance provides a <code>value</code> method for appending - * values to the - * text, and a <code>key</code> - * method for adding keys before values in objects. There are <code>array</code> - * and <code>endArray</code> methods that make and bound array values, and - * <code>object</code> and <code>endObject</code> methods which make and bound - * object values. All of these methods return the JSONWriter instance, - * permitting cascade style. For example, <pre> - * myString = new JSONStringer() - * .object() - * .key("JSON") - * .value("Hello, World!") - * .endObject() - * .toString();</pre> which produces the string <pre> - * {"JSON":"Hello, World!"}</pre> - * <p> - * The first method called must be <code>array</code> or <code>object</code>. - * There are no methods for adding commas or colons. JSONStringer adds them for - * you. Objects and arrays can be nested up to 20 levels deep. - * <p> - * This can sometimes be easier than using a JSONObject to build a string. - * @author JSON.org - * @version 2008-09-18 - */ -public class JSONStringer extends JSONWriter { - /** - * Make a fresh JSONStringer. It can be used to build one JSON text. - */ - public JSONStringer() { - super(new StringWriter()); - } - - /** - * Return the JSON text. This method is used to obtain the product of the - * JSONStringer instance. It will return <code>null</code> if there was a - * problem in the construction of the JSON text (such as the calls to - * <code>array</code> were not properly balanced with calls to - * <code>endArray</code>). - * @return The JSON text. - */ - @Override - public String toString() { - return this.mode == 'd' ? this.writer.toString() : null; - } -} +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.wicket.ajax.json; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// Note: this class was written without inspecting the non-free org.json sourcecode. + +/** + * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most + * application developers should use those methods directly and disregard this + * API. For example:<pre> + * JSONObject object = ... + * String json = object.toString();</pre> + * + * <p>Stringers only encode well-formed JSON strings. In particular: + * <ul> + * <li>The stringer must have exactly one top-level array or object. + * <li>Lexical scopes must be balanced: every call to {@link #array} must + * have a matching call to {@link #endArray} and every call to {@link + * #object} must have a matching call to {@link #endObject}. + * <li>Arrays may not contain keys (property names). + * <li>Objects must alternate keys (property names) and values. + * <li>Values are inserted with either literal {@link #value(Object) value} + * calls, or by nesting arrays or objects. + * </ul> + * Calls that would result in a malformed JSON string will fail with a + * {@link JSONException}. + * + * <p>This class provides no facility for pretty-printing (ie. indenting) + * output. To encode indented output, use {@link JSONObject#toString(int)} or + * {@link JSONArray#toString(int)}. + * + * <p>Some implementations of the API support at most 20 levels of nesting. + * Attempts to create more than 20 levels of nesting may fail with a {@link + * JSONException}. + * + * <p>Each stringer may be used to encode a single top level value. 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 JSONStringer { + + /** + * The output data, containing at most one top-level array or object. + */ + final StringBuilder out = new StringBuilder(); + + /** + * Lexical scoping elements within this stringer, necessary to insert the + * appropriate separator characters (ie. commas and colons) and to detect + * nesting errors. + */ + enum Scope { + + /** + * An array with no elements requires no separators or newlines before + * it is closed. + */ + EMPTY_ARRAY, + + /** + * A array with at least one value requires a comma and newline before + * the next element. + */ + NONEMPTY_ARRAY, + + /** + * An object with no keys or values requires no separators or newlines + * before it is closed. + */ + EMPTY_OBJECT, + + /** + * An object whose most recent element is a key. The next element must + * be a value. + */ + DANGLING_KEY, + + /** + * An object with at least one name/value pair requires a comma and + * newline before the next element. + */ + NONEMPTY_OBJECT, + + /** + * A special bracketless array needed by JSONStringer.join() and + * JSONObject.quote() only. Not used for JSON encoding. + */ + NULL, + } + + /** + * Unlike the original implementation, this stack isn't limited to 20 + * levels of nesting. + */ + private final List<Scope> stack = new ArrayList<Scope>(); + + /** + * A string containing a full set of spaces for a single level of + * indentation, or null for no pretty printing. + */ + private final String indent; + + public JSONStringer() { + indent = null; + } + + JSONStringer(int indentSpaces) { + char[] indentChars = new char[indentSpaces]; + Arrays.fill(indentChars, ' '); + indent = new String(indentChars); + } + + /** + * Begins encoding a new array. Each call to this method must be paired with + * a call to {@link #endArray}. + * + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer array() throws JSONException { + return open(Scope.EMPTY_ARRAY, "["); + } + + /** + * Ends encoding the current array. + * + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer endArray() throws JSONException { + return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); + } + + /** + * Begins encoding a new object. Each call to this method must be paired + * with a call to {@link #endObject}. + * + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer object() throws JSONException { + return open(Scope.EMPTY_OBJECT, "{"); + } + + /** + * Ends encoding the current object. + * + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer endObject() throws JSONException { + return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); + } + + /** + * Enters a new scope by appending any necessary whitespace and the given + * bracket. + */ + JSONStringer open(Scope empty, String openBracket) throws JSONException { + if (stack.isEmpty() && out.length() > 0) { + throw new JSONException("Nesting problem: multiple top-level roots"); + } + beforeValue(); + stack.add(empty); + out.append(openBracket); + return this; + } + + /** + * Closes the current scope by appending any necessary whitespace and the + * given bracket. + */ + JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { + Scope context = peek(); + if (context != nonempty && context != empty) { + throw new JSONException("Nesting problem"); + } + + stack.remove(stack.size() - 1); + if (context == nonempty) { + newline(); + } + out.append(closeBracket); + return this; + } + + /** + * Returns the value on the top of the stack. + */ + private Scope peek() throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + return stack.get(stack.size() - 1); + } + + /** + * Replace the value on the top of the stack with the given value. + */ + private void replaceTop(Scope topOfStack) { + stack.set(stack.size() - 1, topOfStack); + } + + /** + * Encodes {@code value}. + * + * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs} + * or {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer value(Object value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + + if (value instanceof JSONArray) { + ((JSONArray) value).writeTo(this); + return this; + + } else if (value instanceof JSONObject) { + ((JSONObject) value).writeTo(this); + return this; + } + + beforeValue(); + + if (value == null + || value instanceof Boolean + || value == JSONObject.NULL) { + out.append(value); + + } else if (value instanceof Number) { + out.append(JSONObject.numberToString((Number) value)); + + } else { + // 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().indexOf("JsonFunction") != -1){ + string(value.toString(), false); + }else{ + string(value.toString()); + } + } + + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @param value The value to encode. + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer value(boolean value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities}. + * @return this stringer. + * @throws JSONException On internal errors. Shouldn't happen. + */ + public JSONStringer value(double value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(JSONObject.numberToString(value)); + return this; + } + + /** + * Encodes {@code value} to this stringer. + * + * @param value The value to encode. + * @return this stringer. + * @throws JSONException If we have an internal error. Shouldn't happen. + */ + public JSONStringer value(long value) throws JSONException { + if (stack.isEmpty()) { + throw new JSONException("Nesting problem"); + } + beforeValue(); + out.append(value); + return this; + } + + private void string(String value) { + string(value, true); + } + + private void string(String value, boolean surroundingQuotes) { + if(surroundingQuotes){ + out.append("\""); + } + char previousChar = 0; + char currentChar = 0; + for (int i = 0, length = value.length(); i < length; i++) { + previousChar = currentChar; + currentChar = value.charAt(i); + + /* + * From RFC 4627, "All Unicode characters may be placed within the + * quotation marks except for the characters that must be escaped: + * quotation mark, reverse solidus, and the control characters + * (U+0000 through U+001F)." + */ + switch (currentChar) { + case '"': + case '\\': + out.append("\\").append(currentChar); + break; + + case '/': + // It is not required to escape /, but to place it in script tags "</" has to be escaped + if(previousChar == '<'){ + out.append('\\'); + } + out.append(currentChar); + break; + + case '\t': + out.append("\\t"); + break; + + case '\b': + out.append("\\b"); + break; + + case '\n': + out.append("\\n"); + break; + + case '\r': + out.append("\\r"); + break; + + case '\f': + out.append("\\f"); + break; + + default: + if (currentChar <= 0x1F) { + out.append(String.format("\\u%04x", (int) currentChar)); + } else { + out.append(currentChar); + } + break; + } + + } + if(surroundingQuotes){ + out.append("\""); + } + } + + private void newline() { + if (indent == null) { + return; + } + + out.append("\n"); + for (int i = 0; i < stack.size(); i++) { + out.append(indent); + } + } + + /** + * Encodes the key (property name) to this stringer. + * + * @param name the name of the forthcoming value. May not be null. + * @return this stringer. + * @throws JSONException on internal errors, shouldn't happen. + */ + public JSONStringer key(String name) throws JSONException { + if (name == null) { + throw new JSONException("Names must be non-null"); + } + beforeKey(); + string(name); + return this; + } + + /** + * Inserts any necessary separators and whitespace before a name. Also + * adjusts the stack to expect the key's value. + */ + private void beforeKey() throws JSONException { + Scope context = peek(); + if (context == Scope.NONEMPTY_OBJECT) { // first in object + out.append(','); + } else if (context != Scope.EMPTY_OBJECT) { // not in an object! + throw new JSONException("Nesting problem"); + } + newline(); + replaceTop(Scope.DANGLING_KEY); + } + + /** + * Inserts any necessary separators and whitespace before a literal value, + * inline array, or inline object. Also adjusts the stack to expect either a + * closing bracket or another element. + */ + private void beforeValue() throws JSONException { + if (stack.isEmpty()) { + return; + } + + Scope context = peek(); + if (context == Scope.EMPTY_ARRAY) { // first in array + replaceTop(Scope.NONEMPTY_ARRAY); + newline(); + } else if (context == Scope.NONEMPTY_ARRAY) { // another in array + out.append(','); + newline(); + } else if (context == Scope.DANGLING_KEY) { // value for key + out.append(indent == null ? ":" : ": "); + replaceTop(Scope.NONEMPTY_OBJECT); + } else if (context != Scope.NULL) { + throw new JSONException("Nesting problem"); + } + } + + /** + * Returns the encoded JSON string. + * + * <p>If invoked with unterminated arrays or unclosed objects, this method's + * return value is undefined. + * + * <p><strong>Warning:</strong> although it contradicts the general contract + * of {@link Object#toString}, this method returns null if the stringer + * contains no data. + */ + @Override + public String toString() { + return out.length() == 0 ? null : out.toString(); + } +}
http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/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 old mode 100755 new mode 100644 index 3369c6d..58a46d9 --- 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 @@ -1,447 +1,658 @@ -package org.apache.wicket.ajax.json; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringReader; - /* -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: + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +package org.apache.wicket.ajax.json; -The Software shall be used for Good, not Evil. +// Note: this class was written without inspecting the non-free org.json sourcecode. -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.Reader; /** - * 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 2012-02-16 + * 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(); + * String query = object.getString("query"); + * 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: + * <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>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>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 + * information. */ public class JSONTokener { - private long character; - private boolean eof; - private long index; - private long line; - private char previous; - private Reader reader; - private boolean usePrevious; - - /** - * Construct a JSONTokener from a Reader. - * - * @param reader A reader. + * The input JSON. */ - public JSONTokener(Reader reader) { - this.reader = reader.markSupported() - ? reader - : new BufferedReader(reader); - this.eof = false; - this.usePrevious = false; - this.previous = 0; - this.index = 0; - this.character = 1; - this.line = 1; - } + 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; /** - * Construct a JSONTokener from an InputStream. + * @param in JSON encoded string. Null is not permitted and will yield a + * tokener that throws {@code NullPointerExceptions} when methods are + * called. */ - public JSONTokener(InputStream inputStream) throws JSONException { - this(new InputStreamReader(inputStream)); + 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; + } /** - * Construct a JSONTokener from a string. + * Returns the next value from the input. * - * @param s A source string. + * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, + * Integer, Long, Double or {@link JSONObject#NULL}. + * @throws JSONException if the input is malformed. */ - public JSONTokener(String s) { - this(new StringReader(s)); - } + public Object nextValue() throws JSONException { + int c = nextCleanInternal(); + switch (c) { + case -1: + throw syntaxError("End of input"); + case '{': + return readObject(); - /** - * 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() throws JSONException { - if (this.usePrevious || this.index <= 0) { - throw new JSONException("Stepping back two steps is not supported"); + case '[': + return readArray(); + + case '\'': + case '"': + return nextString((char) c); + + default: + pos--; + return readLiteral(); } - this.index -= 1; - this.character -= 1; - this.usePrevious = true; - this.eof = false; } + private int nextCleanInternal() throws JSONException { + while (pos < in.length()) { + int c = in.charAt(pos++); + switch (c) { + case '\t': + case ' ': + case '\n': + case '\r': + continue; - /** - * 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. - */ - public 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); + 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; - } - public boolean end() { - return this.eof && !this.usePrevious; + 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. + * 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. */ - public boolean more() throws JSONException { - this.next(); - if (this.end()) { - return false; + private void skipToEndOfLine() { + for (; pos < in.length(); pos++) { + char c = in.charAt(pos); + if (c == '\r' || c == '\n') { + pos++; + break; + } } - this.back(); - return true; } - /** - * Get the next character in the source string. + * 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. * - * @return The next character, or 0 if past the end of the source string. + * @param quote either ' or ". + * @return The unescaped string. + * @throws JSONException if the string isn't terminated by a closing quote correctly. */ - public char next() throws JSONException { - int c; - if (this.usePrevious) { - this.usePrevious = false; - c = this.previous; - } else { - try { - c = this.reader.read(); - } catch (IOException exception) { - throw new JSONException(exception); + 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 <= 0) { // End of stream - this.eof = true; - c = 0; + 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; } } - this.index += 1; - if (this.previous == '\r') { - this.line += 1; - this.character = c == '\n' ? 0 : 1; - } else if (c == '\n') { - this.line += 1; - this.character = 0; - } else { - this.character += 1; - } - this.previous = (char) c; - return this.previous; - } + throw syntaxError("Unterminated string"); + } /** - * Consume the next character, and check that it matches a specified - * character. - * @param c The character to match. - * @return The character. - * @throws JSONException if the character does not match. + * 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". */ - public char next(char c) throws JSONException { - char n = this.next(); - if (n != c) { - throw this.syntaxError("Expected '" + c + "' and instead saw '" + - 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; } - return n; } - /** - * Get the next n characters. - * - * @param n The number of characters to take. - * @return A string of n characters. - * @throws JSONException - * Substring bounds error if there are not - * n characters remaining in the source string. + * 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. */ - public String next(int n) throws JSONException { - if (n == 0) { - return ""; - } + 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; + } - char[] chars = new char[n]; - int pos = 0; + /* 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. + */ + } + } - while (pos < n) { - chars[pos] = this.next(); - if (this.end()) { - throw this.syntaxError("Substring bounds error"); - } - pos += 1; - } - return new String(chars); - } + /* ...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 + } /** - * Get the next char in the string, skipping whitespace. - * @throws JSONException - * @return A character, or 0 if there are no more characters. + * Returns the string up to but not including any of the given characters or + * a newline character. This does not consume the excluded character. */ - public char nextClean() throws JSONException { - for (;;) { - char c = this.next(); - if (c == 0 || c > ' ') { - return c; + 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); } - /** - * 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> <small>(double quote)</small> or - * <code>'</code> <small>(single quote)</small>. - * @return A String. - * @throws JSONException Unterminated string. + * Reads a sequence of key/value pairs and the trailing closing brace '}' of + * an object. The opening brace '{' should have already been read. */ - public String nextString(char quote) throws JSONException { - char c; - StringBuffer sb = new StringBuffer(); - for (;;) { - c = this.next(); - switch (c) { - case 0: - case '\n': - case '\r': - throw this.syntaxError("Unterminated string"); - case '\\': - c = this.next(); - switch (c) { - case 'b': - sb.append('\b'); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'f': - sb.append('\f'); - break; - case 'r': - sb.append('\r'); - break; - case 'u': - sb.append((char)Integer.parseInt(this.next(4), 16)); - break; - case '"': - case '\'': - case '\\': - case '/': - sb.append(c); - break; - default: - throw this.syntaxError("Illegal escape."); - } - break; - default: - if (c == quote) { - return sb.toString(); + 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()); } - sb.append(c); + } + + /* + * 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"); } } } - /** - * Get the text up but not including the specified character or the - * end of line, whichever comes first. - * @param delimiter A delimiter character. - * @return A string. + * 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]". */ - public String nextTo(char delimiter) throws JSONException { - StringBuffer sb = new StringBuffer(); - for (;;) { - char c = this.next(); - if (c == delimiter || c == 0 || c == '\n' || c == '\r') { - if (c != 0) { - this.back(); - } - return sb.toString().trim(); + private JSONArray readArray() throws JSONException { + JSONArray result = new JSONArray(); + + /* to cover input that ends with ",]". */ + boolean hasTrailingSeparator = false; + + while (true) { + switch (nextCleanInternal()) { + case -1: + throw syntaxError("Unterminated array"); + case ']': + if (hasTrailingSeparator) { + result.put(null); + } + return result; + case ',': + case ';': + /* A separator without a value first means "null". */ + result.put(null); + hasTrailingSeparator = true; + continue; + default: + pos--; + } + + result.put(nextValue()); + + switch (nextCleanInternal()) { + case ']': + return result; + case ',': + case ';': + hasTrailingSeparator = true; + continue; + default: + throw syntaxError("Unterminated array"); } - sb.append(c); } } + /** + * 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); + } /** - * Get the text up but not including one of the specified delimiter - * characters or the end of line, whichever comes first. - * @param delimiters A set of delimiter characters. - * @return A string, trimmed. + * Returns the current position and the entire input string. */ - public String nextTo(String delimiters) throws JSONException { - char c; - StringBuffer sb = new StringBuffer(); - for (;;) { - c = this.next(); - if (delimiters.indexOf(c) >= 0 || c == 0 || - c == '\n' || c == '\r') { - if (c != 0) { - this.back(); - } - return sb.toString().trim(); - } - sb.append(c); - } + @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. + */ /** - * Get the next value. The value can be a Boolean, Double, Integer, - * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. - * @throws JSONException If syntax error. + * Returns true until the input has been exhausted. * - * @return An object. + * @return true if more input exists. */ - public Object nextValue() throws JSONException { - char c = this.nextClean(); - String string; - - switch (c) { - case '"': - case '\'': - return this.nextString(c); - case '{': - this.back(); - return new JSONObject(this); - case '[': - this.back(); - return new JSONArray(this); - } + public boolean more() { + return pos < in.length(); + } - /* - * 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. - */ + /** + * 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'; + } - StringBuffer sb = new StringBuffer(); - while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { - sb.append(c); - c = this.next(); + /** + * 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); } - this.back(); + return result; + } - string = sb.toString().trim(); - if ("".equals(string)) { - throw this.syntaxError("Missing value"); - } - return JSONObject.stringToValue(string); + /** + * 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; + } /** - * 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. + * 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 char skipTo(char to) throws JSONException { - char c; - try { - long startIndex = this.index; - long startCharacter = this.character; - long startLine = this.line; - this.reader.mark(1000000); - do { - c = this.next(); - if (c == 0) { - this.reader.reset(); - this.index = startIndex; - this.character = startCharacter; - this.line = startLine; - return c; - } - } while (c != to); - } catch (IOException exc) { - throw new JSONException(exc); + public String nextTo(String excluded) { + if (excluded == null) { + throw new NullPointerException("excluded == null"); } + return nextToInternal(excluded).trim(); + } - this.back(); - return c; + /** + * 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. + */ + public void skipPast(String thru) { + int thruStart = in.indexOf(thru, pos); + pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); + } /** - * Make a JSONException to signal a syntax error. + * 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 message The error message. - * @return A JSONException object, suitable for throwing + * @param to The character we want to skip to. + * @return The value of {@code to} or null. */ - public JSONException syntaxError(String message) { - return new JSONException(message + this.toString()); + 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; + } + } /** - * Make a printable string of this JSONTokener. + * Returns the integer [0..15] value for the given hex character, or -1 + * for non-hex input. * - * @return " at {index} [character {character} line {line}]" + * @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. */ - @Override - public String toString() { - return " at " + this.index + " [character " + this.character + " line " + - this.line + "]"; + 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; + } } } http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONWriter.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONWriter.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONWriter.java deleted file mode 100755 index 7762383..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JSONWriter.java +++ /dev/null @@ -1,327 +0,0 @@ -package org.apache.wicket.ajax.json; - -import java.io.IOException; -import java.io.Writer; - -/* -Copyright (c) 2006 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. -*/ - -/** - * JSONWriter provides a quick and convenient way of producing JSON text. - * The texts produced strictly conform to JSON syntax rules. No whitespace is - * added, so the results are ready for transmission or storage. Each instance of - * JSONWriter can produce one JSON text. - * <p> - * A JSONWriter instance provides a <code>value</code> method for appending - * values to the - * text, and a <code>key</code> - * method for adding keys before values in objects. There are <code>array</code> - * and <code>endArray</code> methods that make and bound array values, and - * <code>object</code> and <code>endObject</code> methods which make and bound - * object values. All of these methods return the JSONWriter instance, - * permitting a cascade style. For example, <pre> - * new JSONWriter(myWriter) - * .object() - * .key("JSON") - * .value("Hello, World!") - * .endObject();</pre> which writes <pre> - * {"JSON":"Hello, World!"}</pre> - * <p> - * The first method called must be <code>array</code> or <code>object</code>. - * There are no methods for adding commas or colons. JSONWriter adds them for - * you. Objects and arrays can be nested up to 20 levels deep. - * <p> - * This can sometimes be easier than using a JSONObject to build a string. - * @author JSON.org - * @version 2011-11-24 - */ -public class JSONWriter { - private static final int maxdepth = 200; - - /** - * The comma flag determines if a comma should be output before the next - * value. - */ - private boolean comma; - - /** - * The current mode. Values: - * 'a' (array), - * 'd' (done), - * 'i' (initial), - * 'k' (key), - * 'o' (object). - */ - protected char mode; - - /** - * The object/array stack. - */ - private final JSONObject stack[]; - - /** - * The stack top index. A value of 0 indicates that the stack is empty. - */ - private int top; - - /** - * The writer that will receive the output. - */ - protected Writer writer; - - /** - * Make a fresh JSONWriter. It can be used to build one JSON text. - */ - public JSONWriter(Writer w) { - this.comma = false; - this.mode = 'i'; - this.stack = new JSONObject[maxdepth]; - this.top = 0; - this.writer = w; - } - - /** - * Append a value. - * @param string A string value. - * @return this - * @throws JSONException If the value is out of sequence. - */ - private JSONWriter append(String string) throws JSONException { - if (string == null) { - throw new JSONException("Null pointer"); - } - if (this.mode == 'o' || this.mode == 'a') { - try { - if (this.comma && this.mode == 'a') { - this.writer.write(','); - } - this.writer.write(string); - } catch (IOException e) { - throw new JSONException(e); - } - if (this.mode == 'o') { - this.mode = 'k'; - } - this.comma = true; - return this; - } - throw new JSONException("Value out of sequence."); - } - - /** - * Begin appending a new array. All values until the balancing - * <code>endArray</code> will be appended to this array. The - * <code>endArray</code> method must be called to mark the array's end. - * @return this - * @throws JSONException If the nesting is too deep, or if the object is - * started in the wrong place (for example as a key or after the end of the - * outermost array or object). - */ - public JSONWriter array() throws JSONException { - if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { - this.push(null); - this.append("["); - this.comma = false; - return this; - } - throw new JSONException("Misplaced array."); - } - - /** - * End something. - * @param mode Mode - * @param c Closing character - * @return this - * @throws JSONException If unbalanced. - */ - private JSONWriter end(char mode, char c) throws JSONException { - if (this.mode != mode) { - throw new JSONException(mode == 'a' - ? "Misplaced endArray." - : "Misplaced endObject."); - } - this.pop(mode); - try { - this.writer.write(c); - } catch (IOException e) { - throw new JSONException(e); - } - this.comma = true; - return this; - } - - /** - * End an array. This method most be called to balance calls to - * <code>array</code>. - * @return this - * @throws JSONException If incorrectly nested. - */ - public JSONWriter endArray() throws JSONException { - return this.end('a', ']'); - } - - /** - * End an object. This method most be called to balance calls to - * <code>object</code>. - * @return this - * @throws JSONException If incorrectly nested. - */ - public JSONWriter endObject() throws JSONException { - return this.end('k', '}'); - } - - /** - * Append a key. The key will be associated with the next value. In an - * object, every value must be preceded by a key. - * @param string A key string. - * @return this - * @throws JSONException If the key is out of place. For example, keys - * do not belong in arrays or if the key is null. - */ - public JSONWriter key(String string) throws JSONException { - if (string == null) { - throw new JSONException("Null key."); - } - if (this.mode == 'k') { - try { - this.stack[this.top - 1].putOnce(string, Boolean.TRUE); - if (this.comma) { - this.writer.write(','); - } - this.writer.write(JSONObject.quote(string)); - this.writer.write(':'); - this.comma = false; - this.mode = 'o'; - return this; - } catch (IOException e) { - throw new JSONException(e); - } - } - throw new JSONException("Misplaced key."); - } - - - /** - * Begin appending a new object. All keys and values until the balancing - * <code>endObject</code> will be appended to this object. The - * <code>endObject</code> method must be called to mark the object's end. - * @return this - * @throws JSONException If the nesting is too deep, or if the object is - * started in the wrong place (for example as a key or after the end of the - * outermost array or object). - */ - public JSONWriter object() throws JSONException { - if (this.mode == 'i') { - this.mode = 'o'; - } - if (this.mode == 'o' || this.mode == 'a') { - this.append("{"); - this.push(new JSONObject()); - this.comma = false; - return this; - } - throw new JSONException("Misplaced object."); - - } - - - /** - * Pop an array or object scope. - * @param c The scope to close. - * @throws JSONException If nesting is wrong. - */ - private void pop(char c) throws JSONException { - if (this.top <= 0) { - throw new JSONException("Nesting error."); - } - char m = this.stack[this.top - 1] == null ? 'a' : 'k'; - if (m != c) { - throw new JSONException("Nesting error."); - } - this.top -= 1; - this.mode = this.top == 0 - ? 'd' - : this.stack[this.top - 1] == null - ? 'a' - : 'k'; - } - - /** - * Push an array or object scope. - * @param c The scope to open. - * @throws JSONException If nesting is too deep. - */ - private void push(JSONObject jo) throws JSONException { - if (this.top >= maxdepth) { - throw new JSONException("Nesting too deep."); - } - this.stack[this.top] = jo; - this.mode = jo == null ? 'a' : 'k'; - this.top += 1; - } - - - /** - * Append either the value <code>true</code> or the value - * <code>false</code>. - * @param b A boolean. - * @return this - * @throws JSONException - */ - public JSONWriter value(boolean b) throws JSONException { - return this.append(b ? "true" : "false"); - } - - /** - * Append a double value. - * @param d A double. - * @return this - * @throws JSONException If the number is not finite. - */ - public JSONWriter value(double d) throws JSONException { - return this.value(new Double(d)); - } - - /** - * Append a long value. - * @param l A long. - * @return this - * @throws JSONException - */ - public JSONWriter value(long l) throws JSONException { - return this.append(Long.toString(l)); - } - - - /** - * Append an object value. - * @param object The object to append. It can be null, or a Boolean, Number, - * String, JSONObject, or JSONArray, or an object that implements JSONString. - * @return this - * @throws JSONException If the value is out of sequence. - */ - public JSONWriter value(Object object) throws JSONException { - return this.append(JSONObject.valueToString(object)); - } -} http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/wicket-core/src/main/java/org/apache/wicket/ajax/json/JsonSequenceStringer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JsonSequenceStringer.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/JsonSequenceStringer.java deleted file mode 100644 index be56a57..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/JsonSequenceStringer.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.wicket.ajax.json; - -import java.io.IOException; - -import org.apache.wicket.util.io.StringBufferWriter; - -/** - * An efficient implementation of a JSON stringer. The efficiency comes from the fact that istead of - * a {@link String} this class can return a {@link CharSequence}. This is better for downstream - * method that can accept a {@link CharSequence} becuase it requires one less memory copy of the - * internal {@link AppendingStringBufferWriter} to a {@link String} to get the JSON. - * - * @author igor - */ -public class JsonSequenceStringer extends JSONWriter -{ - public JsonSequenceStringer() - { - super(new StringBufferWriter()); - } - - /** - * @return JSON text as a {@link CharSequence} - */ - public CharSequence toCharSequence() - { - - if (mode != 'd') - return null; - - try - { - writer.flush(); - } - catch (IOException e) - { - throw new RuntimeException(e); - } - return ((StringBufferWriter)writer).getStringBuffer(); - } -} http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/wicket-core/src/main/java/org/apache/wicket/ajax/json/README ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/README b/wicket-core/src/main/java/org/apache/wicket/ajax/json/README index d820f64..ebfe78d 100644 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/README +++ b/wicket-core/src/main/java/org/apache/wicket/ajax/json/README @@ -1,5 +1,5 @@ -All JSON** classes are copied from https://github.com/douglascrockford/JSON-java. -Last update: May 18 2012 +All JSON** classes are copied from https://github.com/tdunning/open-json. +Last update: November 25 2016 -Json** are our custom. +Json** are our custom, used in JSONStringer#L261 JsonFunction is borrowed from https://github.com/ivaynberg/wicket-select2. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/0ba87f06/wicket-core/src/main/java/org/apache/wicket/ajax/json/XML.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/ajax/json/XML.java b/wicket-core/src/main/java/org/apache/wicket/ajax/json/XML.java deleted file mode 100755 index b8705d6..0000000 --- a/wicket-core/src/main/java/org/apache/wicket/ajax/json/XML.java +++ /dev/null @@ -1,508 +0,0 @@ -package org.apache.wicket.ajax.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.util.Iterator; - - -/** - * This provides static methods to convert an XML text into a JSONObject, - * and to covert a JSONObject into an XML text. - * @author JSON.org - * @version 2011-02-11 - */ -public class XML { - - /** The Character '&'. */ - public static final Character AMP = new Character('&'); - - /** The Character '''. */ - public static final Character APOS = new Character('\''); - - /** The Character '!'. */ - public static final Character BANG = new Character('!'); - - /** The Character '='. */ - public static final Character EQ = new Character('='); - - /** The Character '>'. */ - public static final Character GT = new Character('>'); - - /** The Character '<'. */ - public static final Character LT = new Character('<'); - - /** The Character '?'. */ - public static final Character QUEST = new Character('?'); - - /** The Character '"'. */ - public static final Character QUOT = new Character('"'); - - /** The Character '/'. */ - public static final Character SLASH = new Character('/'); - - /** - * Replace special characters with XML escapes: - * <pre> - * & <small>(ampersand)</small> is replaced by &amp; - * < <small>(less than)</small> is replaced by &lt; - * > <small>(greater than)</small> is replaced by &gt; - * " <small>(double quote)</small> is replaced by &quot; - * </pre> - * @param string The string to be escaped. - * @return The escaped string. - */ - public static String escape(String string) { - StringBuffer sb = new StringBuffer(); - for (int i = 0, length = string.length(); i < length; i++) { - char c = string.charAt(i); - switch (c) { - case '&': - sb.append("&"); - break; - case '<': - sb.append("<"); - break; - case '>': - sb.append(">"); - break; - case '"': - sb.append("""); - break; - case '\'': - sb.append("'"); - break; - default: - sb.append(c); - } - } - return sb.toString(); - } - - /** - * Throw an exception if the string contains whitespace. - * Whitespace is not allowed in tagNames and attributes. - * @param string - * @throws JSONException - */ - public static void noSpace(String string) throws JSONException { - int i, length = string.length(); - if (length == 0) { - throw new JSONException("Empty string."); - } - for (i = 0; i < length; i += 1) { - if (Character.isWhitespace(string.charAt(i))) { - throw new JSONException("'" + string + - "' contains a space character."); - } - } - } - - /** - * Scan the content following the named tag, attaching it to the context. - * @param x The XMLTokener containing the source string. - * @param context The JSONObject that will include the new material. - * @param name The tag name. - * @return true if the close tag is processed. - * @throws JSONException - */ - private static boolean parse(XMLTokener x, JSONObject context, - String name) throws JSONException { - char c; - int i; - JSONObject jsonobject = null; - String string; - String tagName; - Object token; - -// Test for and skip past these forms: -// <!-- ... --> -// <! ... > -// <![ ... ]]> -// <? ... ?> -// Report errors for these forms: -// <> -// <= -// << - - token = x.nextToken(); - -// <! - - if (token == BANG) { - c = x.next(); - if (c == '-') { - if (x.next() == '-') { - x.skipPast("-->"); - return false; - } - x.back(); - } else if (c == '[') { - token = x.nextToken(); - if ("CDATA".equals(token)) { - if (x.next() == '[') { - string = x.nextCDATA(); - if (string.length() > 0) { - context.accumulate("content", string); - } - return false; - } - } - throw x.syntaxError("Expected 'CDATA['"); - } - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after '<!'."); - } else if (token == LT) { - i += 1; - } else if (token == GT) { - i -= 1; - } - } while (i > 0); - return false; - } else if (token == QUEST) { - -// <? - - x.skipPast("?>"); - return false; - } else if (token == SLASH) { - -// Close tag </ - - token = x.nextToken(); - if (name == null) { - throw x.syntaxError("Mismatched close tag " + token); - } - if (!token.equals(name)) { - throw x.syntaxError("Mismatched " + name + " and " + token); - } - if (x.nextToken() != GT) { - throw x.syntaxError("Misshaped close tag"); - } - return true; - - } else if (token instanceof Character) { - throw x.syntaxError("Misshaped tag"); - -// Open tag < - - } else { - tagName = (String)token; - token = null; - jsonobject = new JSONObject(); - for (;;) { - if (token == null) { - token = x.nextToken(); - } - -// attribute = value - - if (token instanceof String) { - string = (String)token; - token = x.nextToken(); - if (token == EQ) { - token = x.nextToken(); - if (!(token instanceof String)) { - throw x.syntaxError("Missing value"); - } - jsonobject.accumulate(string, - XML.stringToValue((String)token)); - token = null; - } else { - jsonobject.accumulate(string, ""); - } - -// Empty tag <.../> - - } else if (token == SLASH) { - if (x.nextToken() != GT) { - throw x.syntaxError("Misshaped tag"); - } - if (jsonobject.length() > 0) { - context.accumulate(tagName, jsonobject); - } else { - context.accumulate(tagName, ""); - } - return false; - -// Content, between <...> and </...> - - } else if (token == GT) { - for (;;) { - token = x.nextContent(); - if (token == null) { - if (tagName != null) { - throw x.syntaxError("Unclosed tag " + tagName); - } - return false; - } else if (token instanceof String) { - string = (String)token; - if (string.length() > 0) { - jsonobject.accumulate("content", - XML.stringToValue(string)); - } - -// Nested element - - } else if (token == LT) { - if (parse(x, jsonobject, tagName)) { - if (jsonobject.length() == 0) { - context.accumulate(tagName, ""); - } else if (jsonobject.length() == 1 && - jsonobject.opt("content") != null) { - context.accumulate(tagName, - jsonobject.opt("content")); - } else { - context.accumulate(tagName, jsonobject); - } - return false; - } - } - } - } else { - throw x.syntaxError("Misshaped tag"); - } - } - } - } - - - /** - * Try to convert a string into a number, boolean, or null. If the string - * can't be converted, return the string. This is much less ambitious than - * JSONObject.stringToValue, especially because it does not attempt to - * convert plus forms, octal forms, hex forms, or E forms lacking decimal - * points. - * @param string A String. - * @return A simple JSON value. - */ - public static Object stringToValue(String string) { - if ("".equals(string)) { - return string; - } - if ("true".equalsIgnoreCase(string)) { - return Boolean.TRUE; - } - if ("false".equalsIgnoreCase(string)) { - return Boolean.FALSE; - } - if ("null".equalsIgnoreCase(string)) { - return JSONObject.NULL; - } - if ("0".equals(string)) { - return new Integer(0); - } - -// If it might be a number, try converting it. If that doesn't work, -// return the string. - - try { - char initial = string.charAt(0); - boolean negative = false; - if (initial == '-') { - initial = string.charAt(1); - negative = true; - } - if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') { - return string; - } - if ((initial >= '0' && initial <= '9')) { - if (string.indexOf('.') >= 0) { - return Double.valueOf(string); - } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) { - Long myLong = new Long(string); - if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); - } else { - return myLong; - } - } - } - } catch (Exception ignore) { - } - return string; - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject. Some information may be lost in this transformation - * because JSON is a data format and XML is a document format. XML uses - * elements, attributes, and content text, while JSON uses unordered - * collections of name/value pairs and arrays of values. JSON does not - * does not like to distinguish between elements and attributes. - * Sequences of similar elements are represented as JSONArrays. Content - * text may be placed in a "content" member. Comments, prologs, DTDs, and - * <code><[ [ ]]></code> are ignored. - * @param string The source string. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string); - while (x.more() && x.skipPast("<")) { - parse(x, jo, null); - } - return jo; - } - - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * @param object A JSONObject. - * @return A string. - * @throws JSONException - */ - public static String toString(Object object) throws JSONException { - return toString(object, null); - } - - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * @param object A JSONObject. - * @param tagName The optional name of the enclosing tag. - * @return A string. - * @throws JSONException - */ - public static String toString(Object object, String tagName) - throws JSONException { - StringBuffer sb = new StringBuffer(); - int i; - JSONArray ja; - JSONObject jo; - String key; - Iterator keys; - int length; - String string; - Object value; - if (object instanceof JSONObject) { - -// Emit <tagName> - - if (tagName != null) { - sb.append('<'); - sb.append(tagName); - sb.append('>'); - } - -// Loop thru the keys. - - jo = (JSONObject)object; - keys = jo.keys(); - while (keys.hasNext()) { - key = keys.next().toString(); - value = jo.opt(key); - if (value == null) { - value = ""; - } - if (value instanceof String) { - string = (String)value; - } else { - string = null; - } - -// Emit content in body - - if ("content".equals(key)) { - if (value instanceof JSONArray) { - ja = (JSONArray)value; - length = ja.length(); - for (i = 0; i < length; i += 1) { - if (i > 0) { - sb.append('\n'); - } - sb.append(escape(ja.get(i).toString())); - } - } else { - sb.append(escape(value.toString())); - } - -// Emit an array of similar keys - - } else if (value instanceof JSONArray) { - ja = (JSONArray)value; - length = ja.length(); - for (i = 0; i < length; i += 1) { - value = ja.get(i); - if (value instanceof JSONArray) { - sb.append('<'); - sb.append(key); - sb.append('>'); - sb.append(toString(value)); - sb.append("</"); - sb.append(key); - sb.append('>'); - } else { - sb.append(toString(value, key)); - } - } - } else if ("".equals(value)) { - sb.append('<'); - sb.append(key); - sb.append("/>"); - -// Emit a new tag <k> - - } else { - sb.append(toString(value, key)); - } - } - if (tagName != null) { - -// Emit the </tagname> close tag - - sb.append("</"); - sb.append(tagName); - sb.append('>'); - } - return sb.toString(); - -// XML does not have good support for arrays. If an array appears in a place -// where XML is lacking, synthesize an <array> element. - - } else { - if (object.getClass().isArray()) { - object = new JSONArray(object); - } - if (object instanceof JSONArray) { - ja = (JSONArray)object; - length = ja.length(); - for (i = 0; i < length; i += 1) { - sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName)); - } - return sb.toString(); - } else { - string = (object == null) ? "null" : escape(object.toString()); - return (tagName == null) ? "\"" + string + "\"" : - (string.length() == 0) ? "<" + tagName + "/>" : - "<" + tagName + ">" + string + "</" + tagName + ">"; - } - } - } -} \ No newline at end of file
