From d84a853a5f561e0b22a1c589f79da934ecc0f30b Mon Sep 17 00:00:00 2001 From: hansonr Date: Mon, 20 Aug 2018 00:39:18 -0500 Subject: [PATCH] Adds org.json (along with "org.json.simple") for jalview.io.JSONFile.java --- build-libjs.xml | 2 +- libjs/json-site.zip | Bin 0 -> 44450 bytes libjs/jsonsimple-site.zip | Bin 13751 -> 0 bytes srcjar/org/json/CDL.java | 287 ++++ srcjar/org/json/Cookie.java | 169 ++ srcjar/org/json/CookieList.java | 86 + srcjar/org/json/HTTP.java | 162 ++ srcjar/org/json/HTTPTokener.java | 77 + srcjar/org/json/JSONArray.java | 1541 +++++++++++++++++ srcjar/org/json/JSONException.java | 45 + srcjar/org/json/JSONML.java | 542 ++++++ srcjar/org/json/JSONObject.java | 2563 +++++++++++++++++++++++++++++ srcjar/org/json/JSONPointer.java | 293 ++++ srcjar/org/json/JSONPointerException.java | 45 + srcjar/org/json/JSONPropertyIgnore.java | 43 + srcjar/org/json/JSONPropertyName.java | 47 + srcjar/org/json/JSONString.java | 18 + srcjar/org/json/JSONStringer.java | 79 + srcjar/org/json/JSONTokener.java | 529 ++++++ srcjar/org/json/JSONWriter.java | 418 +++++ srcjar/org/json/LICENSE | 23 + srcjar/org/json/Property.java | 75 + srcjar/org/json/README.md | 136 ++ srcjar/org/json/XML.java | 683 ++++++++ srcjar/org/json/XMLTokener.java | 407 +++++ 25 files changed, 8269 insertions(+), 1 deletion(-) create mode 100644 libjs/json-site.zip delete mode 100644 libjs/jsonsimple-site.zip create mode 100644 srcjar/org/json/CDL.java create mode 100644 srcjar/org/json/Cookie.java create mode 100644 srcjar/org/json/CookieList.java create mode 100644 srcjar/org/json/HTTP.java create mode 100644 srcjar/org/json/HTTPTokener.java create mode 100644 srcjar/org/json/JSONArray.java create mode 100644 srcjar/org/json/JSONException.java create mode 100644 srcjar/org/json/JSONML.java create mode 100644 srcjar/org/json/JSONObject.java create mode 100644 srcjar/org/json/JSONPointer.java create mode 100644 srcjar/org/json/JSONPointerException.java create mode 100644 srcjar/org/json/JSONPropertyIgnore.java create mode 100644 srcjar/org/json/JSONPropertyName.java create mode 100644 srcjar/org/json/JSONString.java create mode 100644 srcjar/org/json/JSONStringer.java create mode 100644 srcjar/org/json/JSONTokener.java create mode 100644 srcjar/org/json/JSONWriter.java create mode 100644 srcjar/org/json/LICENSE create mode 100644 srcjar/org/json/Property.java create mode 100644 srcjar/org/json/README.md create mode 100644 srcjar/org/json/XML.java create mode 100644 srcjar/org/json/XMLTokener.java diff --git a/build-libjs.xml b/build-libjs.xml index d8edc9f..7dede70 100644 --- a/build-libjs.xml +++ b/build-libjs.xml @@ -36,7 +36,7 @@ - + Zipping up ${json.zip} diff --git a/libjs/json-site.zip b/libjs/json-site.zip new file mode 100644 index 0000000000000000000000000000000000000000..b78d588102acd74c5ea816cc93b5baa03598b6bf GIT binary patch literal 44450 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Ȍe^A_GGNuJtCb4k4bNmCh*NY>)MgZ)<&-Fbz@?} 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 F{XYa| + * Each row of text represents a row in a table or a data record. Each row + * ends with a NEWLINE character. Each row contains one or more values. + * Values are separated by commas. A value can contain any character except + * for comma, unless is is wrapped in single quotes or double quotes. + *

+ * The first row usually contains the names of the columns. + *

+ * A comma delimited list can be converted into a JSONArray of JSONObjects. + * The names for the elements in the JSONObjects can be taken from the names + * in the first row. + * @author JSON.org + * @version 2016-05-01 + */ +public class CDL { + + /** + * Get the next value. The value can be wrapped in quotes. The value can + * be empty. + * @param x A JSONTokener of the source text. + * @return The value string, or null if empty. + * @throws JSONException if the quoted string is badly formed. + */ + private static String getValue(JSONTokener x) throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = x.next(); + } while (c == ' ' || c == '\t'); + switch (c) { + case 0: + return null; + case '"': + case '\'': + q = c; + sb = new StringBuffer(); + for (;;) { + c = x.next(); + if (c == q) { + //Handle escaped double-quote + char nextC = x.next(); + if(nextC != '\"') { + // if our quote was the end of the file, don't step + if(nextC > 0) { + x.back(); + } + break; + } + } + if (c == 0 || c == '\n' || c == '\r') { + throw x.syntaxError("Missing close quote '" + q + "'."); + } + sb.append(c); + } + return sb.toString(); + case ',': + x.back(); + return ""; + default: + x.back(); + return x.nextTo(','); + } + } + + /** + * Produce a JSONArray of strings from a row of comma delimited values. + * @param x A JSONTokener of the source text. + * @return A JSONArray of strings. + * @throws JSONException + */ + public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { + JSONArray ja = new JSONArray(); + for (;;) { + String value = getValue(x); + char c = x.next(); + if (value == null || + (ja.length() == 0 && value.length() == 0 && c != ',')) { + return null; + } + ja.put(value); + for (;;) { + if (c == ',') { + break; + } + if (c != ' ') { + if (c == '\n' || c == '\r' || c == 0) { + return ja; + } + throw x.syntaxError("Bad character '" + c + "' (" + + (int)c + ")."); + } + c = x.next(); + } + } + } + + /** + * Produce a JSONObject from a row of comma delimited text, using a + * parallel JSONArray of strings to provides the names of the elements. + * @param names A JSONArray of names. This is commonly obtained from the + * first row of a comma delimited text file using the rowToJSONArray + * method. + * @param x A JSONTokener of the source text. + * @return A JSONObject combining the names and values. + * @throws JSONException + */ + public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) + throws JSONException { + JSONArray ja = rowToJSONArray(x); + return ja != null ? ja.toJSONObject(names) : null; + } + + /** + * Produce a comma delimited text row from a JSONArray. Values containing + * the comma character will be quoted. Troublesome characters may be + * removed. + * @param ja A JSONArray of strings. + * @return A string ending in NEWLINE. + */ + public static String rowToString(JSONArray ja) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < ja.length(); i += 1) { + if (i > 0) { + sb.append(','); + } + Object object = ja.opt(i); + if (object != null) { + String string = object.toString(); + if (string.length() > 0 && (string.indexOf(',') >= 0 || + string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || + string.indexOf(0) >= 0 || string.charAt(0) == '"')) { + sb.append('"'); + int length = string.length(); + for (int j = 0; j < length; j += 1) { + char c = string.charAt(j); + if (c >= ' ' && c != '"') { + sb.append(c); + } + } + sb.append('"'); + } else { + sb.append(string); + } + } + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, + * using the first row as a source of names. + * @param x The JSONTokener containing the comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONTokener x) throws JSONException { + return toJSONArray(rowToJSONArray(x), x); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, String string) + throws JSONException { + return toJSONArray(names, new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string + * using a supplied JSONArray as the source of element names. + * @param names A JSONArray of strings. + * @param x A JSONTokener of the source text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, JSONTokener x) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (;;) { + JSONObject jo = rowToJSONObject(names, x); + if (jo == null) { + break; + } + ja.put(jo); + } + if (ja.length() == 0) { + return null; + } + return ja; + } + + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects. The + * first row will be a list of names obtained by inspecting the first + * JSONObject. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + JSONObject jo = ja.optJSONObject(0); + if (jo != null) { + JSONArray names = jo.names(); + if (names != null) { + return rowToString(names) + toString(names, ja); + } + } + return null; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects using + * a provided list of names. The list of names is not included in the + * output. + * @param names A JSONArray of strings. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray names, JSONArray ja) + throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + JSONObject jo = ja.optJSONObject(i); + if (jo != null) { + sb.append(rowToString(jo.toJSONArray(names))); + } + } + return sb.toString(); + } +} diff --git a/srcjar/org/json/Cookie.java b/srcjar/org/json/Cookie.java new file mode 100644 index 0000000..348dc68 --- /dev/null +++ b/srcjar/org/json/Cookie.java @@ -0,0 +1,169 @@ +package org.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. +*/ + +/** + * Convert a web browser cookie specification to a JSONObject and back. + * JSON and Cookies are both notations for name/value pairs. + * @author JSON.org + * @version 2015-12-09 + */ +public class Cookie { + + /** + * Produce a copy of a string in which the characters '+', '%', '=', ';' + * and control characters are replaced with "%hh". This is a gentle form + * of URL encoding, attempting to cause as little distortion to the + * string as possible. The characters '=' and ';' are meta characters in + * cookies. By convention, they are escaped using the URL-encoding. This is + * only a convention, not a standard. Often, cookies are expected to have + * encoded values. We encode '=' and ';' because we must. We encode '%' and + * '+' because they are meta characters in URL encoding. + * @param string The source string. + * @return The escaped result. + */ + public static String escape(String string) { + char c; + String s = string.trim(); + int length = s.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i += 1) { + c = s.charAt(i); + if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { + sb.append('%'); + sb.append(Character.forDigit((char)((c >>> 4) & 0x0f), 16)); + sb.append(Character.forDigit((char)(c & 0x0f), 16)); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + + /** + * Convert a cookie specification string into a JSONObject. The string + * will contain a name value pair separated by '='. The name and the value + * will be unescaped, possibly converting '+' and '%' sequences. The + * cookie properties may follow, separated by ';', also represented as + * name=value (except the secure property, which does not have a value). + * The name will be stored under the key "name", and the value will be + * stored under the key "value". This method does not do checking or + * validation of the parameters. It only converts the cookie string into + * a JSONObject. + * @param string The cookie specification string. + * @return A JSONObject containing "name", "value", and possibly other + * members. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + String name; + JSONObject jo = new JSONObject(); + Object value; + JSONTokener x = new JSONTokener(string); + jo.put("name", x.nextTo('=')); + x.next('='); + jo.put("value", x.nextTo(';')); + x.next(); + while (x.more()) { + name = unescape(x.nextTo("=;")); + if (x.next() != '=') { + if (name.equals("secure")) { + value = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } + } else { + value = unescape(x.nextTo(';')); + x.next(); + } + jo.put(name, value); + } + return jo; + } + + + /** + * Convert a JSONObject into a cookie specification string. The JSONObject + * must contain "name" and "value" members. + * If the JSONObject contains "expires", "domain", "path", or "secure" + * members, they will be appended to the cookie specification string. + * All other members are ignored. + * @param jo A JSONObject + * @return A cookie specification string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + + sb.append(escape(jo.getString("name"))); + sb.append("="); + sb.append(escape(jo.getString("value"))); + if (jo.has("expires")) { + sb.append(";expires="); + sb.append(jo.getString("expires")); + } + if (jo.has("domain")) { + sb.append(";domain="); + sb.append(escape(jo.getString("domain"))); + } + if (jo.has("path")) { + sb.append(";path="); + sb.append(escape(jo.getString("path"))); + } + if (jo.optBoolean("secure")) { + sb.append(";secure"); + } + return sb.toString(); + } + + /** + * Convert %hh sequences to single characters, and + * convert plus to space. + * @param string A string that may contain + * + (plus) and + * %hh sequences. + * @return The unescaped string. + */ + public static String unescape(String string) { + int length = string.length(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; ++i) { + char c = string.charAt(i); + if (c == '+') { + c = ' '; + } else if (c == '%' && i + 2 < length) { + int d = JSONTokener.dehexchar(string.charAt(i + 1)); + int e = JSONTokener.dehexchar(string.charAt(i + 2)); + if (d >= 0 && e >= 0) { + c = (char)(d * 16 + e); + i += 2; + } + } + sb.append(c); + } + return sb.toString(); + } +} diff --git a/srcjar/org/json/CookieList.java b/srcjar/org/json/CookieList.java new file mode 100644 index 0000000..c67ee3a --- /dev/null +++ b/srcjar/org/json/CookieList.java @@ -0,0 +1,86 @@ +package org.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. + */ + +/** + * Convert a web browser cookie list string to a JSONObject and back. + * @author JSON.org + * @version 2015-12-09 + */ +public class CookieList { + + /** + * Convert a cookie list into a JSONObject. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The names and the values + * will be unescaped, possibly converting '+' and '%' sequences. + * + * To add a cookie to a cookie list, + * cookielistJSONObject.put(cookieJSONObject.getString("name"), + * cookieJSONObject.getString("value")); + * @param string A cookie list string + * @return A JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + JSONTokener x = new JSONTokener(string); + while (x.more()) { + String name = Cookie.unescape(x.nextTo('=')); + x.next('='); + jo.put(name, Cookie.unescape(x.nextTo(';'))); + x.next(); + } + return jo; + } + + /** + * Convert a JSONObject into a cookie list. A cookie list is a sequence + * of name/value pairs. The names are separated from the values by '='. + * The pairs are separated by ';'. The characters '%', '+', '=', and ';' + * in the names and values are replaced by "%hh". + * @param jo A JSONObject + * @return A cookie list string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + boolean b = false; + final StringBuilder sb = new StringBuilder(); + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { + final Object value = jo.opt(key); + if (!JSONObject.NULL.equals(value)) { + if (b) { + sb.append(';'); + } + sb.append(Cookie.escape(key)); + sb.append("="); + sb.append(Cookie.escape(value.toString())); + b = true; + } + } + return sb.toString(); + } +} diff --git a/srcjar/org/json/HTTP.java b/srcjar/org/json/HTTP.java new file mode 100644 index 0000000..84ed53b --- /dev/null +++ b/srcjar/org/json/HTTP.java @@ -0,0 +1,162 @@ +package org.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.Locale; + +/** + * Convert an HTTP header to a JSONObject and back. + * @author JSON.org + * @version 2015-12-09 + */ +public class HTTP { + + /** Carriage return/line feed. */ + public static final String CRLF = "\r\n"; + + /** + * Convert an HTTP header string into a JSONObject. It can be a request + * header or a response header. A request header will contain + *

{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header will contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * In addition, the other parameters in the header will be captured, using + * the HTTP field names as JSON names, so that
+     *    Date: Sun, 26 May 2002 18:06:04 GMT
+     *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
+     *    Cache-Control: no-cache
+ * become + *
{...
+     *    Date: "Sun, 26 May 2002 18:06:04 GMT",
+     *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
+     *    "Cache-Control": "no-cache",
+     * ...}
+ * It does no further checking or conversion. It does not parse dates. + * It does not do '%' transforms on URLs. + * @param string An HTTP header string. + * @return A JSONObject containing the elements and attributes + * of the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + HTTPTokener x = new HTTPTokener(string); + String token; + + token = x.nextToken(); + if (token.toUpperCase(Locale.ROOT).startsWith("HTTP")) { + +// Response + + jo.put("HTTP-Version", token); + jo.put("Status-Code", x.nextToken()); + jo.put("Reason-Phrase", x.nextTo('\0')); + x.next(); + + } else { + +// Request + + jo.put("Method", token); + jo.put("Request-URI", x.nextToken()); + jo.put("HTTP-Version", x.nextToken()); + } + +// Fields + + while (x.more()) { + String name = x.nextTo(':'); + x.next(':'); + jo.put(name, x.nextTo('\0')); + x.next(); + } + return jo; + } + + + /** + * Convert a JSONObject into an HTTP header. A request header must contain + *
{
+     *    Method: "POST" (for example),
+     *    "Request-URI": "/" (for example),
+     *    "HTTP-Version": "HTTP/1.1" (for example)
+     * }
+ * A response header must contain + *
{
+     *    "HTTP-Version": "HTTP/1.1" (for example),
+     *    "Status-Code": "200" (for example),
+     *    "Reason-Phrase": "OK" (for example)
+     * }
+ * Any other members of the JSONObject will be output as HTTP fields. + * The result will end with two CRLF pairs. + * @param jo A JSONObject + * @return An HTTP header string. + * @throws JSONException if the object does not contain enough + * information. + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + if (jo.has("Status-Code") && jo.has("Reason-Phrase")) { + sb.append(jo.getString("HTTP-Version")); + sb.append(' '); + sb.append(jo.getString("Status-Code")); + sb.append(' '); + sb.append(jo.getString("Reason-Phrase")); + } else if (jo.has("Method") && jo.has("Request-URI")) { + sb.append(jo.getString("Method")); + sb.append(' '); + sb.append('"'); + sb.append(jo.getString("Request-URI")); + sb.append('"'); + sb.append(' '); + sb.append(jo.getString("HTTP-Version")); + } else { + throw new JSONException("Not enough material for an HTTP header."); + } + sb.append(CRLF); + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { + String value = jo.optString(key); + if (!"HTTP-Version".equals(key) && !"Status-Code".equals(key) && + !"Reason-Phrase".equals(key) && !"Method".equals(key) && + !"Request-URI".equals(key) && !JSONObject.NULL.equals(value)) { + sb.append(key); + sb.append(": "); + sb.append(jo.optString(key)); + sb.append(CRLF); + } + } + sb.append(CRLF); + return sb.toString(); + } +} diff --git a/srcjar/org/json/HTTPTokener.java b/srcjar/org/json/HTTPTokener.java new file mode 100644 index 0000000..55f48ff --- /dev/null +++ b/srcjar/org/json/HTTPTokener.java @@ -0,0 +1,77 @@ +package org.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. +*/ + +/** + * The HTTPTokener extends the JSONTokener to provide additional methods + * for the parsing of HTTP headers. + * @author JSON.org + * @version 2015-12-09 + */ +public class HTTPTokener extends JSONTokener { + + /** + * Construct an HTTPTokener from a string. + * @param string A source string. + */ + public HTTPTokener(String string) { + super(string); + } + + + /** + * Get the next token or string. This is used in parsing HTTP headers. + * @throws JSONException + * @return A String. + */ + public String nextToken() throws JSONException { + char c; + char q; + StringBuilder sb = new StringBuilder(); + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == '"' || c == '\'') { + q = c; + for (;;) { + c = next(); + if (c < ' ') { + throw syntaxError("Unterminated string."); + } + if (c == q) { + return sb.toString(); + } + sb.append(c); + } + } + for (;;) { + if (c == 0 || Character.isWhitespace(c)) { + return sb.toString(); + } + sb.append(c); + c = next(); + } + } +} diff --git a/srcjar/org/json/JSONArray.java b/srcjar/org/json/JSONArray.java new file mode 100644 index 0000000..fbc1a0f --- /dev/null +++ b/srcjar/org/json/JSONArray.java @@ -0,0 +1,1541 @@ +package org.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.StringWriter; +import java.io.Writer; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * 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 get and opt + * methods for accessing the values by index, and put methods for + * adding or replacing values. The values can be any of these types: + * Boolean, JSONArray, JSONObject, + * Number, String, or the + * JSONObject.NULL object. + *

+ * The constructor can convert a JSON text into a Java object. The + * toString method converts to JSON text. + *

+ * A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. + *

+ * The texts produced by the toString methods strictly conform to + * JSON syntax rules. The constructors are more forgiving in the texts they will + * accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing bracket.
  • + *
  • The null value will be inserted when there is , + *  (comma) elision.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • 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: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, or + * null.
  • + *
+ * + * @author JSON.org + * @version 2016-08/15 + */ +public class JSONArray implements Iterable { + + /** + * The arrayList where the JSONArray's properties are kept. + */ + private final ArrayList myArrayList; + + /** + * Construct an empty JSONArray. + */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } + + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x + * A JSONTokener + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + + char nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar != ']') { + x.back(); + for (;;) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); + } + switch (x.nextClean()) { + case 0: + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + case ',': + nextChar = x.nextClean(); + if (nextChar == 0) { + // array is unclosed. No ']' found, instead EOF + throw x.syntaxError("Expected a ',' or ']'"); + } + if (nextChar == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } + + /** + * Construct a JSONArray from a source JSON text. + * + * @param source + * A string that begins with [ (left + * bracket) and ends with ] + *  (right bracket). + * @throws JSONException + * If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONArray from a Collection. + * + * @param collection + * A Collection. + */ + public JSONArray(Collection collection) { + if (collection == null) { + this.myArrayList = new ArrayList(); + } else { + this.myArrayList = new ArrayList(collection.size()); + for (Object o: collection){ + this.myArrayList.add(JSONObject.wrap(o)); + } + } + } + + /** + * Construct a JSONArray from an array + * + * @throws JSONException + * If not an array or if an array value is non-finite number. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + this.myArrayList.ensureCapacity(length); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException( + "JSONArray initial value should be a string or collection or array."); + } + } + + @Override + public Iterator iterator() { + return this.myArrayList.iterator(); + } + + /** + * Get the object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException + * If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = this.opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * 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 JSONException + * If there is no value for the index or if the value is not + * convertible to boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = this.get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("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) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the float value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).floatValue() + : Float.parseFloat(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(int index) throws JSONException { + Object object = this.get(index); + try { + if (object instanceof Number) { + return (Number)object; + } + return JSONObject.stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the enum value associated with an index. + * + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @return The enum value at the index location + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an enum. + */ + public > E getEnum(Class clazz, int index) throws JSONException { + E val = optEnum(clazz, index); + if(val==null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONArray[" + index + "] is not an enum of type " + + JSONObject.quote(clazz.getSimpleName()) + "."); + } + return val; + } + + /** + * Get the BigDecimal 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 BigDecimal. + */ + public BigDecimal getBigDecimal (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigDecimal.", e); + } + } + + /** + * Get the BigInteger 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 BigInteger. + */ + public BigInteger getBigInteger (int index) throws JSONException { + Object object = this.get(index); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + + "] could not convert to BigInteger.", e); + } + } + + /** + * 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 is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException + * If there is no value for the index. or if the value is not a + * JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index + * subscript + * @return A JSONObject value. + * @throws JSONException + * If there is no value for the index or if the value is not a + * JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("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) throws JSONException { + Object object = this.get(index); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number.", e); + } + } + + /** + * Get the string associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException + * If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = this.get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * 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 JSONObject.NULL.equals(this.opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The + * separator 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 JSONException + * If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = this.length(); + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index + * The index must be between 0 and length() - 1. If not, null is returned. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= this.length()) ? null : this.myArrayList + .get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false + * if there is no value at that index, or if the value is not Boolean.TRUE + * or the String "true". + * + * @param index + * The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return this.optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the + * defaultValue if there is no value at that index or if it is not a Boolean + * or the String "true" or "false" (case insensitive). + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return this.getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return this.optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).doubleValue(); + } + if (val instanceof String) { + try { + return Double.parseDouble((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional float value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public float optFloat(int index) { + return this.optFloat(index, Float.NaN); + } + + /** + * Get the optional float value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * subscript + * @param defaultValue + * The default value. + * @return The value. + */ + public float optFloat(int index, float defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).floatValue(); + } + if (val instanceof String) { + try { + return Float.parseFloat((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional int value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return this.optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).intValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal(val.toString()).intValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @return The enum value at the index location or null if not found + */ + public > E optEnum(Class clazz, int index) { + return this.optEnum(clazz, index, null); + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default in case the value is not found + * @return The enum value at the index location or defaultValue if + * the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, int index, E defaultValue) { + try { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException e) { + return defaultValue; + } catch (NullPointerException e) { + return defaultValue; + } + } + + + /** + * Get the optional BigInteger value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigInteger optBigInteger(int index, BigInteger defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigInteger){ + return (BigInteger) val; + } + if (val instanceof BigDecimal){ + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float){ + return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return BigInteger.valueOf(((Number) val).longValue()); + } + try { + final String valStr = val.toString(); + if(JSONObject.isDecimalNotation(valStr)) { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional BigDecimal value associated with an index. The + * defaultValue is returned if there is no value for the index, or if the + * value is not a number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public BigDecimal optBigDecimal(int index, BigDecimal defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigDecimal){ + return (BigDecimal) val; + } + if (val instanceof BigInteger){ + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float){ + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return new BigDecimal(((Number) val).longValue()); + } + try { + return new BigDecimal(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index + * subscript + * @return A JSONArray value, or null if the index has no value, or if the + * value is not a JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = this.opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if + * the key is not found, or null if the index has no value, or if the value + * is not a JSONObject. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = this.opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if + * there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return this.optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is + * returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).longValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal(val.toString()).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional {@link Number} value associated with a key, or null + * if there is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param index + * The index must be between 0 and length() - 1. + * @return An object which is the value. + */ + public Number optNumber(int index) { + return this.optNumber(index, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public Number optNumber(int index, Number defaultValue) { + Object val = this.opt(index); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return (Number) val; + } + + if (val instanceof String) { + try { + return JSONObject.stringToNumber((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional string value associated with an index. It returns an + * empty string if there is no value at that index. If the value is not a + * string and is not null, then it is converted to a string. + * + * @param index + * The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return this.optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is + * returned if the key is not found. + * + * @param index + * The index must be between 0 and length() - 1. + * @param defaultValue + * The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = this.opt(index); + return JSONObject.NULL.equals(object) ? defaultValue : object + .toString(); + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value + * A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + return this.put(value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + */ + public JSONArray put(Collection value) { + return this.put(new JSONArray(value)); + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value + * A double value. + * @return this. + * @throws JSONException + * if the value is not finite. + */ + public JSONArray put(double value) throws JSONException { + return this.put(Double.valueOf(value)); + } + + /** + * Append a float value. This increases the array's length by one. + * + * @param value + * A float value. + * @return this. + * @throws JSONException + * if the value is not finite. + */ + public JSONArray put(float value) throws JSONException { + return this.put(Float.valueOf(value)); + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value + * An int value. + * @return this. + */ + public JSONArray put(int value) { + return this.put(Integer.valueOf(value)); + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value + * A long value. + * @return this. + */ + public JSONArray put(long value) { + return this.put(Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which + * is produced from a Map. + * + * @param value + * A Map value. + * @return this. + * @throws JSONException + * If a value in the map is non-finite number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONArray put(Map value) { + return this.put(new JSONObject(value)); + } + + /** + * 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 this. + * @throws JSONException + * If the value is non-finite number. + */ + public JSONArray put(Object value) { + JSONObject.testValidity(value); + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean 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 + * A boolean value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + return this.put(index, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which + * is produced from a Collection. + * + * @param index + * The subscript. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + return this.put(index, new JSONArray(value)); + } + + /** + * Put or replace a double value. 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 + * A double value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, double value) throws JSONException { + return this.put(index, Double.valueOf(value)); + } + + /** + * Put or replace a float value. 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 + * A float value. + * @return this. + * @throws JSONException + * If the index is negative or if the value is non-finite. + */ + public JSONArray put(int index, float value) throws JSONException { + return this.put(index, Float.valueOf(value)); + } + + /** + * Put or replace an int value. 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 + * An int value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + return this.put(index, Integer.valueOf(value)); + } + + /** + * Put or replace a long value. 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 + * A long value. + * @return this. + * @throws JSONException + * If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + return this.put(index, Long.valueOf(value)); + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that + * is produced from a Map. + * + * @param index + * The subscript. + * @param value + * The Map value. + * @return this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONArray put(int index, Map value) throws JSONException { + this.put(index, new JSONObject(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 this. + * @throws JSONException + * If the index is negative or if the the value is an invalid + * number. + */ + public JSONArray put(int index, Object value) throws JSONException { + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < this.length()) { + JSONObject.testValidity(value); + this.myArrayList.set(index, value); + return this; + } + if(index == this.length()){ + // simple append + return this.put(value); + } + // if we are inserting past the length, we want to grow the array all at once + // instead of incrementally. + this.myArrayList.ensureCapacity(index + 1); + while (index != this.length()) { + // we don't need to test validity of NULL objects + this.myArrayList.add(JSONObject.NULL); + } + return this.put(value); + } + + /** + * Creates a JSONPointer using an initialization string and tries to + * match it to an item within this JSONArray. For example, given a + * JSONArray initialized with this document: + *
+     * [
+     *     {"b":"c"}
+     * ]
+     * 
+ * and this JSONPointer string: + *
+     * "/0/b"
+     * 
+ * Then this method will return the String "c" + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + + /** + * Uses a uaer initialized JSONPointer and tries to + * match it to an item whithin this JSONArray. For example, given a + * JSONArray initialized with this document: + *
+     * [
+     *     {"b":"c"}
+     * ]
+     * 
+ * and this JSONPointer: + *
+     * "/0/b"
+     * 
+ * Then this method will return the String "c" + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Remove an index and close the hole. + * + * @param index + * The index of the element to be removed. + * @return The value that was associated with the index, or null if there + * was no value. + */ + public Object remove(int index) { + return index >= 0 && index < this.length() + ? this.myArrayList.remove(index) + : null; + } + + /** + * Determine if two JSONArrays are similar. + * They must contain similar sequences. + * + * @param other The other JSONArray + * @return true if they are equal + */ + public boolean similar(Object other) { + if (!(other instanceof JSONArray)) { + return false; + } + int len = this.length(); + if (len != ((JSONArray)other).length()) { + return false; + } + for (int i = 0; i < len; i += 1) { + Object valueThis = this.myArrayList.get(i); + Object valueOther = ((JSONArray)other).myArrayList.get(i); + if(valueThis == valueOther) { + continue; + } + if(valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of + * this JSONArray. + * + * @param names + * A JSONArray containing a list of key strings. These will be + * paired with the values. + * @return A JSONObject, or null if there are no names or if this JSONArray + * has no values. + * @throws JSONException + * If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.isEmpty() || this.isEmpty()) { + return null; + } + JSONObject jo = new JSONObject(names.length()); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * 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. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, transmittable representation of the + * array. + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONArray. + * + *

If indentFactor > 0 and the {@link JSONArray} has only + * one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

If an array has 2 or more elements, then it will be output across + * multiple lines:

{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the + * object, beginning with [ (left + * bracket) and ending with ] + *  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + StringWriter sw = new StringWriter(); + synchronized (sw.getBuffer()) { + return this.write(sw, indentFactor, 0).toString(); + } + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + /** + * Write the contents of the JSONArray as JSON text to a writer. + * + *

If indentFactor > 0 and the {@link JSONArray} has only + * one element, then the array will be output on a single line: + *

{@code [1]}
+ * + *

If an array has 2 or more elements, then it will be output across + * multiple lines:

{@code
+     * [
+     * 1,
+     * "value 2",
+     * 3
+     * ]
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer + * Writes the serialized JSON + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + int length = this.length(); + writer.write('['); + + if (length == 1) { + try { + JSONObject.writeValue(writer, this.myArrayList.get(0), + indentFactor, indent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONArray value at index: 0", e); + } + } else if (length != 0) { + final int newindent = indent + indentFactor; + + for (int i = 0; i < length; i += 1) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, newindent); + try { + JSONObject.writeValue(writer, this.myArrayList.get(i), + indentFactor, newindent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONArray value at index: " + i, e); + } + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + JSONObject.indent(writer, indent); + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); + } + } + + /** + * Returns a java.util.List containing all of the elements in this array. + * If an element in the array is a JSONArray or JSONObject it will also + * be converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.List containing the elements of this array + */ + public List toList() { + List results = new ArrayList(this.myArrayList.size()); + for (Object element : this.myArrayList) { + if (element == null || JSONObject.NULL.equals(element)) { + results.add(null); + } else if (element instanceof JSONArray) { + results.add(((JSONArray) element).toList()); + } else if (element instanceof JSONObject) { + results.add(((JSONObject) element).toMap()); + } else { + results.add(element); + } + } + return results; + } + + /** + * Check if JSONArray is empty. + * + * @return true if JSONArray is empty, otherwise false. + */ + public boolean isEmpty() { + return myArrayList.isEmpty(); + } + +} diff --git a/srcjar/org/json/JSONException.java b/srcjar/org/json/JSONException.java new file mode 100644 index 0000000..72542df --- /dev/null +++ b/srcjar/org/json/JSONException.java @@ -0,0 +1,45 @@ +package org.json; + +/** + * The JSONException is thrown by the JSON.org classes when things are amiss. + * + * @author JSON.org + * @version 2015-12-09 + */ +public class JSONException extends RuntimeException { + /** Serialization ID */ + private static final long serialVersionUID = 0; + + /** + * Constructs a JSONException with an explanatory message. + * + * @param message + * Detail about the reason for the exception. + */ + public JSONException(final String message) { + super(message); + } + + /** + * Constructs a JSONException with an explanatory message and cause. + * + * @param message + * Detail about the reason for the exception. + * @param cause + * The cause. + */ + public JSONException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Constructs a new JSONException with the specified cause. + * + * @param cause + * The cause. + */ + public JSONException(final Throwable cause) { + super(cause.getMessage(), cause); + } + +} diff --git a/srcjar/org/json/JSONML.java b/srcjar/org/json/JSONML.java new file mode 100644 index 0000000..acec7b8 --- /dev/null +++ b/srcjar/org/json/JSONML.java @@ -0,0 +1,542 @@ +package org.json; + +/* +Copyright (c) 2008 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. +*/ + +/** + * This provides static methods to convert an XML text into a JSONArray or + * JSONObject, and to covert a JSONArray or JSONObject into an XML text using + * the JsonML transform. + * + * @author JSON.org + * @version 2016-01-30 + */ +public class JSONML { + /** + * Parse XML values and store them in a JSONArray. + * @param x The XMLTokener containing the source string. + * @param arrayForm true if array form, false if object form. + * @param ja The JSONArray that is containing the current tag or null + * if we are at the outermost level. + * @param keepStrings Don't type-convert text nodes and attribute values + * @return A JSONArray if the value is the outermost tag, otherwise null. + * @throws JSONException + */ + private static Object parse( + XMLTokener x, + boolean arrayForm, + JSONArray ja, + boolean keepStrings + ) throws JSONException { + String attribute; + char c; + String closeTag = null; + int i; + JSONArray newja = null; + JSONObject newjo = null; + Object token; + String tagName = null; + +// Test for and skip past these forms: +// +// +// +// + + while (true) { + if (!x.more()) { + throw x.syntaxError("Bad XML"); + } + token = x.nextContent(); + if (token == XML.LT) { + token = x.nextToken(); + if (token instanceof Character) { + if (token == XML.SLASH) { + +// Close tag "); + } else { + x.back(); + } + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA") && x.next() == '[') { + if (ja != null) { + ja.put(x.nextCDATA()); + } + } else { + throw x.syntaxError("Expected 'CDATA['"); + } + } else { + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + } + } else if (token == XML.QUEST) { + +// "); + } else { + throw x.syntaxError("Misshaped tag"); + } + +// Open tag < + + } else { + if (!(token instanceof String)) { + throw x.syntaxError("Bad tagName '" + token + "'."); + } + tagName = (String)token; + newja = new JSONArray(); + newjo = new JSONObject(); + if (arrayForm) { + newja.put(tagName); + if (ja != null) { + ja.put(newja); + } + } else { + newjo.put("tagName", tagName); + if (ja != null) { + ja.put(newjo); + } + } + token = null; + for (;;) { + if (token == null) { + token = x.nextToken(); + } + if (token == null) { + throw x.syntaxError("Misshaped tag"); + } + if (!(token instanceof String)) { + break; + } + +// attribute = value + + attribute = (String)token; + if (!arrayForm && ("tagName".equals(attribute) || "childNode".equals(attribute))) { + throw x.syntaxError("Reserved attribute."); + } + token = x.nextToken(); + if (token == XML.EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token)); + token = null; + } else { + newjo.accumulate(attribute, ""); + } + } + if (arrayForm && newjo.length() > 0) { + newja.put(newjo); + } + +// Empty tag <.../> + + if (token == XML.SLASH) { + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (ja == null) { + if (arrayForm) { + return newja; + } + return newjo; + } + +// Content, between <...> and + + } else { + if (token != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + closeTag = (String)parse(x, arrayForm, newja, keepStrings); + if (closeTag != null) { + if (!closeTag.equals(tagName)) { + throw x.syntaxError("Mismatched '" + tagName + + "' and '" + closeTag + "'"); + } + tagName = null; + if (!arrayForm && newja.length() > 0) { + newjo.put("childNodes", newja); + } + if (ja == null) { + if (arrayForm) { + return newja; + } + return newjo; + } + } + } + } + } else { + if (ja != null) { + ja.put(token instanceof String + ? keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token) + : token); + } + } + } + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The source string. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return (JSONArray)parse(new XMLTokener(string), true, null, false); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child tags. + * As opposed to toJSONArray this method does not attempt to convert + * any text node or attribute value to any type + * but just leaves it as a string. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The source string. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(String string, boolean keepStrings) throws JSONException { + return (JSONArray)parse(new XMLTokener(string), true, null, keepStrings); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * As opposed to toJSONArray this method does not attempt to convert + * any text node or attribute value to any type + * but just leaves it as a string. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(XMLTokener x, boolean keepStrings) throws JSONException { + return (JSONArray)parse(x, true, null, keepStrings); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONArray using the JsonML transform. Each XML tag is represented as + * a JSONArray in which the first element is the tag name. If the tag has + * attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and + * JSONArrays will represent the child content and tags. + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONArray + */ + public static JSONArray toJSONArray(XMLTokener x) throws JSONException { + return (JSONArray)parse(x, true, null, false); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return (JSONObject)parse(new XMLTokener(string), false, null, false); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param string The XML source text. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return (JSONObject)parse(new XMLTokener(string), false, null, keepStrings); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener of the XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(XMLTokener x) throws JSONException { + return (JSONObject)parse(x, false, null, false); + } + + + /** + * Convert a well-formed (but not necessarily valid) XML string into a + * JSONObject using the JsonML transform. Each XML tag is represented as + * a JSONObject with a "tagName" property. If the tag has attributes, then + * the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which + * will be an array of strings and JsonML JSONObjects. + + * Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * @param x An XMLTokener of the XML source text. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown on error converting to a JSONObject + */ + public static JSONObject toJSONObject(XMLTokener x, boolean keepStrings) throws JSONException { + return (JSONObject)parse(x, false, null, keepStrings); + } + + + /** + * Reverse the JSONML transformation, making an XML text from a JSONArray. + * @param ja A JSONArray. + * @return An XML string. + * @throws JSONException Thrown on error converting to a string + */ + public static String toString(JSONArray ja) throws JSONException { + int i; + JSONObject jo; + int length; + Object object; + StringBuilder sb = new StringBuilder(); + String tagName; + +// Emit = length) { + sb.append('/'); + sb.append('>'); + } else { + sb.append('>'); + do { + object = ja.get(i); + i += 1; + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject)object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray)object)); + } else { + sb.append(object.toString()); + } + } + } while (i < length); + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONObject. + * The JSONObject must contain a "tagName" property. If it has children, + * then it must have a "childNodes" property containing an array of objects. + * The other properties are attributes with string values. + * @param jo A JSONObject. + * @return An XML string. + * @throws JSONException Thrown on error converting to a string + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuilder sb = new StringBuilder(); + int i; + JSONArray ja; + int length; + Object object; + String tagName; + Object value; + +//Emit '); + } else { + sb.append('>'); + length = ja.length(); + for (i = 0; i < length; i += 1) { + object = ja.get(i); + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject)object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray)object)); + } else { + sb.append(object.toString()); + } + } + } + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } +} diff --git a/srcjar/org/json/JSONObject.java b/srcjar/org/json/JSONObject.java new file mode 100644 index 0000000..8deb6ba --- /dev/null +++ b/srcjar/org/json/JSONObject.java @@ -0,0 +1,2563 @@ +package org.json; + +import java.io.Closeable; + +/* + 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.StringWriter; +import java.io.Writer; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * 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 get and opt methods for accessing + * the values by name, and put methods for adding or replacing + * values by name. The values can be any of these types: Boolean, + * JSONArray, JSONObject, Number, + * String, or the JSONObject.NULL 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 + * get and opt methods, or to convert values into a + * JSON text using the put and toString methods. A + * get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a + * default value instead of throwing an exception, and so is useful for + * obtaining optional values. + *

+ * The generic get() and opt() methods return an + * object, which you can cast or query for type. There are also typed + * get and opt methods that do type checking and type + * coercion for you. The opt methods differ from the get methods in that they + * do not throw. Instead, they return a specified value, such as null. + *

+ * The put methods add or replace values in an object. For + * example, + * + *

+ * myString = new JSONObject()
+ *         .put("JSON", "Hello, World!").toString();
+ * 
+ * + * produces the string {"JSON": "Hello, World"}. + *

+ * The texts produced by the toString methods strictly conform to + * the JSON syntax rules. The constructors are more forgiving in the texts they + * will accept: + *

    + *
  • An extra , (comma) may appear just + * before the closing brace.
  • + *
  • Strings may be quoted with ' (single + * quote).
  • + *
  • 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: + * { } [ ] / \ : , # and if they do not look like numbers and + * if they are not the reserved words true, false, + * or null.
  • + *
+ * + * @author JSON.org + * @version 2016-08-15 + */ +public 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. + */ + @Override + 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. + */ + @Override + public boolean equals(Object object) { + return object == null || object == this; + } + /** + * A Null object is equal to the null value and to itself. + * + * @return always returns 0. + */ + @Override + public int hashCode() { + return 0; + } + + /** + * Get the "null" string value. + * + * @return The string "null". + */ + @Override + public String toString() { + return "null"; + } + } + + /** + * The map where the JSONObject's properties are kept. + */ + private final Map map; + + /** + * It is sometimes more convenient and less ambiguous to have a + * NULL object than to use Java's null value. + * JSONObject.NULL.equals(null) returns true. + * JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + + /** + * Construct an empty JSONObject. + */ + public JSONObject() { + // HashMap is used on purpose to ensure that elements are unordered by + // the specification. + // JSON tends to be a portable transfer format to allows the container + // implementations to rearrange their items for a faster element + // retrieval based on associative access. + // Therefore, an implementation mustn't rely on the order of the item. + this.map = new HashMap(); + } + + /** + * 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 jo + * A JSONObject. + * @param names + * An array of strings. + */ + public JSONObject(JSONObject jo, String[] names) { + this(names.length); + for (int i = 0; i < names.length; i += 1) { + try { + this.putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x + * A JSONTokener object containing the source string. + * @throws JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (;;) { + 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 ':'. + + c = x.nextClean(); + if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + + // Use syntaxError(..) to include error location + + if (key != null) { + // Check if key exists + if (this.opt(key) != null) { + // key already exists + throw x.syntaxError("Duplicate key \"" + key + "\""); + } + // Only add value if non-null + Object value = x.nextValue(); + if (value!=null) { + this.put(key, value); + } + } + + // Pairs are separated by ','. + + 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 Map. + * + * @param m + * A map object that can be used to initialize the contents of + * the JSONObject. + * @throws JSONException + * If a value in the map is non-finite number. + * @throws NullPointerException + * If a key in the map is null + */ + public JSONObject(Map m) { + if (m == null) { + this.map = new HashMap(); + } else { + this.map = new HashMap(m.size()); + for (final Entry e : m.entrySet()) { + if(e.getKey() == null) { + throw new NullPointerException("Null key."); + } + final Object value = e.getValue(); + if (value != null) { + this.map.put(String.valueOf(e.getKey()), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on + * all of the public methods of the object. For each of the methods with no + * parameters and a name starting with "get" or + * "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the + * new JSONObject. + *

+ * The key is formed by removing the "get" or "is" + * prefix. If the second remaining character is not upper case, then the + * first character is converted to lower case. + *

+ * Methods that are static, return void, + * have parameters, or are "bridge" methods, are ignored. + *

+ * For example, if an object has a method named "getName", and + * if the result of calling object.getName() is + * "Larry Fine", then the JSONObject will contain + * "name": "Larry Fine". + *

+ * The {@link JSONPropertyName} annotation can be used on a bean getter to + * override key name used in the JSONObject. For example, using the object + * above with the getName method, if we annotated it with: + *

+     * @JSONPropertyName("FullName")
+     * public String getName() { return this.name; }
+     * 
+ * The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * Similarly, the {@link JSONPropertyName} annotation can be used on non- + * get and is methods. We can also override key + * name used in the JSONObject as seen below even though the field would normally + * be ignored: + *

+     * @JSONPropertyName("FullName")
+     * public String fullName() { return this.name; }
+     * 
+ * The resulting JSON object would contain "FullName": "Larry Fine" + *

+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean property + * to not be serialized into JSON. If both {@link JSONPropertyIgnore} and + * {@link JSONPropertyName} are defined on the same method, a depth comparison is + * performed and the one closest to the concrete class being serialized is used. + * If both annotations are at the same level, then the {@link JSONPropertyIgnore} + * annotation takes precedent and the field is not serialized. + * For example, the following declaration would prevent the getName + * method from being serialized: + *

+     * @JSONPropertyName("FullName")
+     * @JSONPropertyIgnore 
+     * public String getName() { return this.name; }
+     * 
+ *

+ * + * @param bean + * An object that has getter methods that should be used to make + * a JSONObject. + */ + public JSONObject(Object bean) { + this(); + this.populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the + * public members. The resulting JSONObject's keys will be the strings from + * the names array, and the values will be the field values associated with + * those keys in the object. If a key is not found or not visible, then it + * will not be copied into the new JSONObject. + * + * @param object + * An object that has fields that should be used to make a + * JSONObject. + * @param names + * An array of strings, the names of the fields to be obtained + * from the object. + */ + public JSONObject(Object object, String names[]) { + this(names.length); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + this.putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most + * commonly used JSONObject constructor. + * + * @param source + * A string beginning with { (left + * brace) and ending with } + *  (right brace). + * @exception JSONException + * If there is a syntax error in the source string or a + * duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName + * The ResourceBundle base name. + * @param locale + * The Locale to load the ResourceBundle for. + * @throws JSONException + * If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, + Thread.currentThread().getContextClassLoader()); + +// Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key != null) { + +// Go through the path, ensuring that there is a nested JSONObject for each +// segment except the last. Add the value using the last segment's name into +// the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Constructor to specify an initial capacity of the internal map. Useful for library + * internal calls where we know, or at least can best guess, how big this JSONObject + * will be. + * + * @param initialCapacity initial capacity of the internal map. + */ + protected JSONObject(int initialCapacity){ + this.map = new HashMap(initialCapacity); + } + + /** + * 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. + * + * If only one value is accumulated that is not a JSONArray, then the result + * will be the same as using put. But if multiple values are accumulated, + * then the result will be like append. + * + * @param key + * A key string. + * @param value + * An object to be accumulated under the key. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, + value instanceof JSONArray ? new JSONArray().put(value) + : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + this.put(key, new JSONArray().put(object).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. + * @throws JSONException + * If the value is non-finite number or if the current value associated with + * the key is not a JSONArray. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = this.opt(key); + if (object == null) { + this.put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + this.put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + 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. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; + } + +// Shave off trailing zeros and decimal point, if possible. + + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get the value object associated with a key. + * + * @param key + * A key string. + * @return The object associated with the key. + * @throws JSONException + * if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + Object object = this.opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); + } + return object; + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an enum. + */ + public > E getEnum(Class clazz, String key) throws JSONException { + E val = optEnum(clazz, key); + if(val==null) { + // JSONException should really take a throwable argument. + // If it did, I would re-implement this with the Enum.valueOf + // method and place any thrown exception in the JSONException + throw new JSONException("JSONObject[" + quote(key) + + "] is not an enum of type " + quote(clazz.getSimpleName()) + + "."); + } + return val; + } + + /** + * Get the boolean value associated with a key. + * + * @param key + * A key string. + * @return The truth. + * @throws JSONException + * if the value is not a Boolean or the String "true" or + * "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = this.get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object) + .equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a Boolean."); + } + + /** + * Get the BigInteger value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value cannot + * be converted to BigInteger. + */ + public BigInteger getBigInteger(String key) throws JSONException { + Object object = this.get(key); + try { + return new BigInteger(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigInteger.", e); + } + } + + /** + * Get the BigDecimal value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value + * cannot be converted to BigDecimal. + */ + public BigDecimal getBigDecimal(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof BigDecimal) { + return (BigDecimal)object; + } + try { + return new BigDecimal(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] could not be converted to BigDecimal.", e); + } + } + + /** + * Get the double value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * 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) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).doubleValue() + : Double.parseDouble(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + + /** + * Get the float value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public float getFloat(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).floatValue() + : Float.parseFloat(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + + /** + * Get the Number value associated with a key. + * + * @param key + * A key string. + * @return The numeric value. + * @throws JSONException + * if the key is not found or if the value is not a Number + * object and cannot be converted to a number. + */ + public Number getNumber(String key) throws JSONException { + Object object = this.get(key); + try { + if (object instanceof Number) { + return (Number)object; + } + return stringToNumber(object.toString()); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a number.", e); + } + } + + /** + * Get the int value associated with a key. + * + * @param key + * A key string. + * @return The integer value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to an integer. + */ + public int getInt(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not an int.", e); + } + } + + /** + * Get the JSONArray value associated with a key. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + * @throws JSONException + * if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("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 JSONException + * if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONObject[" + quote(key) + + "] is not a JSONObject."); + } + + /** + * Get the long value associated with a key. + * + * @param key + * A key string. + * @return The long value. + * @throws JSONException + * if the key is not found or if the value cannot be converted + * to a long. + */ + public long getLong(String key) throws JSONException { + Object object = this.get(key); + try { + return object instanceof Number ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + + "] is not a long.", e); + } + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + if (jo.isEmpty()) { + return null; + } + return jo.keySet().toArray(new String[jo.length()]); + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Get the string associated with a key. + * + * @param key + * A key string. + * @return A string which is the value. + * @throws JSONException + * if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = this.get(key); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } + + /** + * 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 this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, + * create one with a value of 1. If there is such a property, and if it is + * an Integer, Long, Double, or Float, then add one to it. + * + * @param key + * A key string. + * @return this. + * @throws JSONException + * If there is already a property with this name that is not an + * Integer, Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = this.opt(key); + if (value == null) { + this.put(key, 1); + } else if (value instanceof BigInteger) { + this.put(key, ((BigInteger)value).add(BigInteger.ONE)); + } else if (value instanceof BigDecimal) { + this.put(key, ((BigDecimal)value).add(BigDecimal.ONE)); + } else if (value instanceof Integer) { + this.put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + this.put(key, ((Long) value).longValue() + 1L); + } else if (value instanceof Double) { + this.put(key, ((Double) value).doubleValue() + 1.0d); + } else if (value instanceof Float) { + this.put(key, ((Float) value).floatValue() + 1.0f); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); + } + return this; + } + + /** + * 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(this.opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. Modifying this key Set will also + * modify the JSONObject. Use with caution. + * + * @see Set#iterator() + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.keySet().iterator(); + } + + /** + * Get a set of keys of the JSONObject. Modifying this key Set will also modify the + * JSONObject. Use with caution. + * + * @see Map#keySet() + * + * @return A keySet. + */ + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get a set of entries of the JSONObject. These are raw values and may not + * match what is returned by the JSONObject get* and opt* functions. Modifying + * the returned EntrySet or the Entry objects contained therein will modify the + * backing JSONObject. This does not return a clone or a read-only view. + * + * Use with caution. + * + * @see Map#entrySet() + * + * @return An Entry Set + */ + protected Set> entrySet() { + return this.map.entrySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Check if JSONObject is empty. + * + * @return true if JSONObject is empty, otherwise false. + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * 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() { + if(this.map.isEmpty()) { + return null; + } + return new JSONArray(this.map.keySet()); + } + + /** + * Produce a string from a Number. + * + * @param number + * A Number + * @return A String. + * @throws JSONException + * If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 + && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * 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. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @return The enum value associated with the key or null if not found + */ + public > E optEnum(Class clazz, String key) { + return this.optEnum(clazz, key, null); + } + + /** + * Get the enum value associated with a key. + * + * @param clazz + * The type of enum to retrieve. + * @param key + * A key string. + * @param defaultValue + * The default in case the value is not found + * @return The enum value associated with the key or defaultValue + * if the value is not found or cannot be assigned to clazz + */ + public > E optEnum(Class clazz, String key, E defaultValue) { + try { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (clazz.isAssignableFrom(val.getClass())) { + // we just checked it! + @SuppressWarnings("unchecked") + E myE = (E) val; + return myE; + } + return Enum.valueOf(clazz, val.toString()); + } catch (IllegalArgumentException e) { + return defaultValue; + } catch (NullPointerException e) { + return defaultValue; + } + } + + /** + * Get an optional boolean associated with a key. It returns false if there + * is no such key, or if the value is not Boolean.TRUE or the String "true". + * + * @param key + * A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return this.optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the + * defaultValue if there is no such key, or if it is not a Boolean or the + * String "true" or "false" (case insensitive). + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Boolean){ + return ((Boolean) val).booleanValue(); + } + try { + // we'll use the get anyway because it does string conversion. + return this.getBoolean(key); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigDecimal associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigDecimal){ + return (BigDecimal) val; + } + if (val instanceof BigInteger){ + return new BigDecimal((BigInteger) val); + } + if (val instanceof Double || val instanceof Float){ + return new BigDecimal(((Number) val).doubleValue()); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return new BigDecimal(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + return new BigDecimal(val.toString()); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional BigInteger associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public BigInteger optBigInteger(String key, BigInteger defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof BigInteger){ + return (BigInteger) val; + } + if (val instanceof BigDecimal){ + return ((BigDecimal) val).toBigInteger(); + } + if (val instanceof Double || val instanceof Float){ + return new BigDecimal(((Number) val).doubleValue()).toBigInteger(); + } + if (val instanceof Long || val instanceof Integer + || val instanceof Short || val instanceof Byte){ + return BigInteger.valueOf(((Number) val).longValue()); + } + // don't check if it's a string in case of unchecked Number subclasses + try { + // the other opt functions handle implicit conversions, i.e. + // jo.put("double",1.1d); + // jo.optInt("double"); -- will return 1, not an error + // this conversion to BigDecimal then to BigInteger is to maintain + // that type cast support that may truncate the decimal. + final String valStr = val.toString(); + if(isDecimalNotation(valStr)) { + return new BigDecimal(valStr).toBigInteger(); + } + return new BigInteger(valStr); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get an optional double associated with a key, or NaN if there is no such + * key or if its value is not a number. If the value is a string, an attempt + * will be made to evaluate it as a number. + * + * @param key + * A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return this.optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if + * there is no such key or if its value is not a number. If the value is a + * string, an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).doubleValue(); + } + if (val instanceof String) { + try { + return Double.parseDouble((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get the optional double value associated with an index. NaN is returned + * if there is no value for the index, or if the value is not a number and + * cannot be converted to a number. + * + * @param key + * A key string. + * @return The value. + */ + public float optFloat(String key) { + return this.optFloat(key, Float.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue + * is returned if there is no value for the index, or if the value is not a + * number and cannot be converted to a number. + * + * @param key + * A key string. + * @param defaultValue + * The default value. + * @return The value. + */ + public float optFloat(String key, float defaultValue) { + Object val = this.opt(key); + if (JSONObject.NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).floatValue(); + } + if (val instanceof String) { + try { + return Float.parseFloat((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional int value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return this.optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).intValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal((String) val).intValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional JSONArray associated with a key. It returns null if there + * is no such key, or if its value is not a JSONArray. + * + * @param key + * A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = this.opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if + * there is no such key, or if its value is not a JSONObject. + * + * @param key + * A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = this.opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no + * such key or if the value is not a number. If the value is a string, an + * attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return this.optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return ((Number) val).longValue(); + } + + if (val instanceof String) { + try { + return new BigDecimal((String) val).longValue(); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional {@link Number} value associated with a key, or null + * if there is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number ({@link BigDecimal}). This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param key + * A key string. + * @return An object which is the value. + */ + public Number optNumber(String key) { + return this.optNumber(key, null); + } + + /** + * Get an optional {@link Number} value associated with a key, or the default if there + * is no such key or if the value is not a number. If the value is a string, + * an attempt will be made to evaluate it as a number. This method + * would be used in cases where type coercion of the number value is unwanted. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return An object which is the value. + */ + public Number optNumber(String key, Number defaultValue) { + Object val = this.opt(key); + if (NULL.equals(val)) { + return defaultValue; + } + if (val instanceof Number){ + return (Number) val; + } + + if (val instanceof String) { + try { + return stringToNumber((String) val); + } catch (Exception e) { + return defaultValue; + } + } + return defaultValue; + } + + /** + * Get an optional string associated with a key. It returns an empty string + * if there is no such key. If the value is not a string and is not null, + * then it is converted to a string. + * + * @param key + * A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return this.optString(key, ""); + } + + /** + * Get an optional string associated with a key. It returns the defaultValue + * if there is no such key. + * + * @param key + * A key string. + * @param defaultValue + * The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = this.opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } + + /** + * Populates the internal map of the JSONObject with the bean properties. The + * bean can not be recursive. + * + * @see JSONObject#JSONObject(Object) + * + * @param bean + * the bean + */ + private void populateMap(Object bean) { + Class klass = bean.getClass(); + + // If klass is a System class then set includeSuperClass to false. + + boolean includeSuperClass = klass.getClassLoader() != null; + + Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods(); + for (final Method method : methods) { + final int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) + && !Modifier.isStatic(modifiers) + && method.getParameterTypes().length == 0 + && !method.isBridge() + && method.getReturnType() != Void.TYPE + && isValidMethodName(method.getName())) { + final String key = getKeyNameFromMethod(method); + if (key != null && !key.isEmpty()) { + try { + final Object result = method.invoke(bean); + if (result != null) { + this.map.put(key, wrap(result)); + // we don't use the result anywhere outside of wrap + // if it's a resource we should be sure to close it + // after calling toString + if (result instanceof Closeable) { + try { + ((Closeable) result).close(); + } catch (IOException ignore) { + } + } + } + } catch (IllegalAccessException ignore) { + } catch (IllegalArgumentException ignore) { + } catch (InvocationTargetException ignore) { + } + } + } + } + } + + private boolean isValidMethodName(String name) { + return !"getClass".equals(name) && !"getDeclaringClass".equals(name); + } + + private String getKeyNameFromMethod(Method method) { + final int ignoreDepth = getAnnotationDepth(method, JSONPropertyIgnore.class); + if (ignoreDepth > 0) { + final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class); + if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) { + // the hierarchy asked to ignore, and the nearest name override + // was higher or non-existent + return null; + } + } + JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class); + if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) { + return annotation.value(); + } + String key; + final String name = method.getName(); + if (name.startsWith("get") && name.length() > 3) { + key = name.substring(3); + } else if (name.startsWith("is") && name.length() > 2) { + key = name.substring(2); + } else { + return null; + } + // if the first letter in the key is not uppercase, then skip. + // This is to maintain backwards compatibility before PR406 + // (https://github.com/stleary/JSON-java/pull/406/) + if (Character.isLowerCase(key.charAt(0))) { + return null; + } + if (key.length() == 1) { + key = key.toLowerCase(Locale.ROOT); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1); + } + return key; + } + + /** + * Searches the class hierarchy to see if the method or it's super + * implementations and interfaces has the annotation. + * + * @param + * type of the annotation + * + * @param m + * method to check + * @param annotationClass + * annotation to look for + * @return the {@link Annotation} if the annotation exists on the current method + * or one of it's super class definitions + */ + private static A getAnnotation(final Method m, final Class annotationClass) { + // if we have invalid data the result is null + if (m == null || annotationClass == null) { + return null; + } + + if (m.isAnnotationPresent(annotationClass)) { + return m.getAnnotation(annotationClass); + } + + // if we've already reached the Object class, return null; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return null; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + return getAnnotation(im, annotationClass); + } catch (final SecurityException ex) { + continue; + } catch (final NoSuchMethodException ex) { + continue; + } + } + + try { + return getAnnotation( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + } catch (final SecurityException ex) { + return null; + } catch (final NoSuchMethodException ex) { + return null; + } + } + + /** + * Searches the class hierarchy to see if the method or it's super + * implementations and interfaces has the annotation. Returns the depth of the + * annotation in the hierarchy. + * + * @param + * type of the annotation + * + * @param m + * method to check + * @param annotationClass + * annotation to look for + * @return Depth of the annotation or -1 if the annotation is not on the method. + */ + private static int getAnnotationDepth(final Method m, final Class annotationClass) { + // if we have invalid data the result is -1 + if (m == null || annotationClass == null) { + return -1; + } + + if (m.isAnnotationPresent(annotationClass)) { + return 1; + } + + // if we've already reached the Object class, return -1; + Class c = m.getDeclaringClass(); + if (c.getSuperclass() == null) { + return -1; + } + + // check directly implemented interfaces for the method being checked + for (Class i : c.getInterfaces()) { + try { + Method im = i.getMethod(m.getName(), m.getParameterTypes()); + int d = getAnnotationDepth(im, annotationClass); + if (d > 0) { + // since the annotation was on the interface, add 1 + return d + 1; + } + } catch (final SecurityException ex) { + continue; + } catch (final NoSuchMethodException ex) { + continue; + } + } + + try { + int d = getAnnotationDepth( + c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()), + annotationClass); + if (d > 0) { + // since the annotation was on the superclass, add 1 + return d + 1; + } + return -1; + } catch (final SecurityException ex) { + return -1; + } catch (final NoSuchMethodException ex) { + return -1; + } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A boolean which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + return this.put(key, value ? Boolean.TRUE : Boolean.FALSE); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONArray which is produced from a Collection. + * + * @param key + * A key string. + * @param value + * A Collection value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Collection value) throws JSONException { + return this.put(key, new JSONArray(value)); + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A double which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, double value) throws JSONException { + return this.put(key, Double.valueOf(value)); + } + + /** + * Put a key/float pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A float which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, float value) throws JSONException { + return this.put(key, Float.valueOf(value)); + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * An int which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + return this.put(key, Integer.valueOf(value)); + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key + * A key string. + * @param value + * A long which is the value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + return this.put(key, Long.valueOf(value)); + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a + * JSONObject which is produced from a Map. + * + * @param key + * A key string. + * @param value + * A Map value. + * @return this. + * @throws JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Map value) throws JSONException { + return this.put(key, new JSONObject(value)); + } + + /** + * 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 JSONException + * If the value is non-finite number. + * @throws NullPointerException + * If the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new NullPointerException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + this.remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null, and only if there is not already a member with that + * name. + * + * @param key string + * @param value object + * @return this. + * @throws JSONException + * if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (this.opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + return this.put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value + * are both non-null. + * + * @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 JSONException + * If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + return this.put(key, value); + } + return this; + } + + /** + * Creates a JSONPointer using an initialization string and tries to + * match it to an item within this JSONObject. For example, given a + * JSONObject initialized with this document: + *

+     * {
+     *     "a":{"b":"c"}
+     * }
+     * 
+ * and this JSONPointer string: + *
+     * "/a/b"
+     * 
+ * Then this method will return the String "c". + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(String jsonPointer) { + return query(new JSONPointer(jsonPointer)); + } + /** + * Uses a user initialized JSONPointer and tries to + * match it to an item within this JSONObject. For example, given a + * JSONObject initialized with this document: + *
+     * {
+     *     "a":{"b":"c"}
+     * }
+     * 
+ * and this JSONPointer: + *
+     * "/a/b"
+     * 
+ * Then this method will return the String "c". + * A JSONPointerException may be thrown from code called by this method. + * + * @param jsonPointer string that can be used to create a JSONPointer + * @return the item matched by the JSONPointer, otherwise null + */ + public Object query(JSONPointer jsonPointer) { + return jsonPointer.queryFrom(this); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer the string representation of the JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(String jsonPointer) { + return optQuery(new JSONPointer(jsonPointer)); + } + + /** + * Queries and returns a value from this object using {@code jsonPointer}, or + * returns null if the query fails due to a missing key. + * + * @param jsonPointer The JSON pointer + * @return the queried value or {@code null} + * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax + */ + public Object optQuery(JSONPointer jsonPointer) { + try { + return jsonPointer.queryFrom(this); + } catch (JSONPointerException e) { + return null; + } + } + + /** + * Produce a string in double quotes with backslash sequences in all the + * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') + || (c >= '\u2000' && c < '\u2100')) { + w.write("\\u"); + hhhh = Integer.toHexString(c); + w.write("0000", 0, 4 - hhhh.length()); + w.write(hhhh); + } else { + w.write(c); + } + } + } + w.write('"'); + return w; + } + + /** + * 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 this.map.remove(key); + } + + /** + * Determine if two JSONObjects are similar. + * They must contain the same set of names which must be associated with + * similar values. + * + * @param other The other JSONObject + * @return true if they are equal + */ + public boolean similar(Object other) { + try { + if (!(other instanceof JSONObject)) { + return false; + } + if (!this.keySet().equals(((JSONObject)other).keySet())) { + return false; + } + for (final Entry entry : this.entrySet()) { + String name = entry.getKey(); + Object valueThis = entry.getValue(); + Object valueOther = ((JSONObject)other).get(name); + if(valueThis == valueOther) { + continue; + } + if(valueThis == null) { + return false; + } + if (valueThis instanceof JSONObject) { + if (!((JSONObject)valueThis).similar(valueOther)) { + return false; + } + } else if (valueThis instanceof JSONArray) { + if (!((JSONArray)valueThis).similar(valueOther)) { + return false; + } + } else if (!valueThis.equals(valueOther)) { + return false; + } + } + return true; + } catch (Throwable exception) { + return false; + } + } + + /** + * Tests if the value should be tried as a decimal. It makes no test if there are actual digits. + * + * @param val value to test + * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise. + */ + protected static boolean isDecimalNotation(final String val) { + return val.indexOf('.') > -1 || val.indexOf('e') > -1 + || val.indexOf('E') > -1 || "-0".equals(val); + } + + /** + * Converts a string to a number using the narrowest possible type. Possible + * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer. + * When a Double is returned, it should always be a valid Double and not NaN or +-infinity. + * + * @param val value to convert + * @return Number representation of the value. + * @throws NumberFormatException thrown if the value is not a valid number. A public + * caller should catch this and wrap it in a {@link JSONException} if applicable. + */ + protected static Number stringToNumber(final String val) throws NumberFormatException { + char initial = val.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + // decimal representation + if (isDecimalNotation(val)) { + // quick dirty way to see if we need a BigDecimal instead of a Double + // this only handles some cases of overflow or underflow + if (val.length()>14) { + return new BigDecimal(val); + } + final Double d = Double.valueOf(val); + if (d.isInfinite() || d.isNaN()) { + // if we can't parse it as a double, go up to BigDecimal + // this is probably due to underflow like 4.32e-678 + // or overflow like 4.65e5324. The size of the string is small + // but can't be held in a Double. + return new BigDecimal(val); + } + return d; + } + // integer representation. + // This will narrow any values to the smallest reasonable Object representation + // (Integer, Long, or BigInteger) + + // string version + // The compare string length method reduces GC, + // but leads to smaller integers being placed in larger wrappers even though not + // needed. i.e. 1,000,000,000 -> Long even though it's an Integer + // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long + //if(val.length()<=9){ + // return Integer.valueOf(val); + //} + //if(val.length()<=18){ + // return Long.valueOf(val); + //} + //return new BigInteger(val); + + // BigInteger version: We use a similar bitLenth compare as + // BigInteger#intValueExact uses. Increases GC, but objects hold + // only what they need. i.e. Less runtime overhead if the value is + // long lived. Which is the better tradeoff? This is closer to what's + // in stringToValue. + BigInteger bi = new BigInteger(val); + if(bi.bitLength()<=31){ + return Integer.valueOf(bi.intValue()); + } + if(bi.bitLength()<=63){ + return Long.valueOf(bi.longValue()); + } + return bi; + } + throw new NumberFormatException("val ["+val+"] is not a valid number."); + } + + /** + * Try to convert a string into a number, boolean, or null. If the string + * can't be converted, return the string. + * + * @param string + * A String. + * @return A simple JSON value. + */ + // Changes to this method must be copied to the corresponding method in + // the XML class to keep full support for Android + public static Object stringToValue(String string) { + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + try { + // if we want full Big Number support this block can be replaced with: + // return stringToNumber(string); + if (isDecimalNotation(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return Integer.valueOf(myLong.intValue()); + } + return myLong; + } + } + } catch (Exception ignore) { + } + } + return string; + } + + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o + * The object to test. + * @throws JSONException + * If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException( + "JSON does not allow non-finite numbers."); + } + } + } + } + + /** + * Produce a JSONArray containing the values of the members of this + * JSONObject. + * + * @param names + * A JSONArray containing a list of key strings. This determines + * the sequence of the values in the result. + * @return A JSONArray of values. + * @throws JSONException + * If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.isEmpty()) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * 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. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + */ + @Override + public String toString() { + try { + return this.toString(0); + } catch (Exception e) { + return null; + } + } + + /** + * Make a pretty-printed JSON text of this JSONObject. + * + *

If indentFactor > 0 and the {@link JSONObject} + * has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

If an object has 2 or more keys, then it will be output across + * multiple lines:

{
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation + * of the object, beginning with { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + StringWriter w = new StringWriter(); + synchronized (w.getBuffer()) { + return this.write(w, indentFactor, 0).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 other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * 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 { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + // moves the implementation to JSONWriter as: + // 1. It makes more sense to be part of the writer class + // 2. For Android support this method is not available. By implementing it in the Writer + // Android users can use the writer with the built in Android JSONObject implementation. + return JSONWriter.valueToString(value); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL + * object. If it is an array or collection, wrap it in a JSONArray. If it is + * a map, wrap it in a JSONObject. If it is a standard property (Double, + * String, et al) then it is already wrapped. Otherwise, if it comes from + * one of the java packages, turn it into a string. And if it doesn't, try + * to wrap it in a JSONObject. If the wrapping fails, then null is returned. + * + * @param object + * The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject || object instanceof JSONArray + || NULL.equals(object) || object instanceof JSONString + || object instanceof Byte || object instanceof Character + || object instanceof Short || object instanceof Integer + || object instanceof Long || object instanceof Boolean + || object instanceof Float || object instanceof Double + || object instanceof String || object instanceof BigInteger + || object instanceof BigDecimal || object instanceof Enum) { + return object; + } + + if (object instanceof Collection) { + Collection coll = (Collection) object; + return new JSONArray(coll); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + Map map = (Map) object; + return new JSONObject(map); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage + .getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For + * compactness, no whitespace is added. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + return this.write(writer, 0, 0); + } + + static final Writer writeValue(Writer writer, Object value, + int indentFactor, int indent) throws JSONException, IOException { + if (value == null || value.equals(null)) { + writer.write("null"); + } else if (value instanceof JSONString) { + Object o; + try { + o = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + writer.write(o != null ? o.toString() : quote(value.toString())); + } else if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary + final String numberAsString = numberToString((Number) value); + try { + // Use the BigDecimal constructor for its parser to validate the format. + @SuppressWarnings("unused") + BigDecimal testNum = new BigDecimal(numberAsString); + // Close enough to a JSON number that we will use it unquoted + writer.write(numberAsString); + } catch (NumberFormatException ex){ + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + quote(numberAsString, writer); + } + } else if (value instanceof Boolean) { + writer.write(value.toString()); + } else if (value instanceof Enum) { + writer.write(quote(((Enum)value).name())); + } else if (value instanceof JSONObject) { + ((JSONObject) value).write(writer, indentFactor, indent); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer, indentFactor, indent); + } else if (value instanceof Map) { + Map map = (Map) value; + new JSONObject(map).write(writer, indentFactor, indent); + } else if (value instanceof Collection) { + Collection coll = (Collection) value; + new JSONArray(coll).write(writer, indentFactor, indent); + } else if (value.getClass().isArray()) { + new JSONArray(value).write(writer, indentFactor, indent); + } else { + quote(value.toString(), writer); + } + return writer; + } + + static final void indent(Writer writer, int indent) throws IOException { + for (int i = 0; i < indent; i += 1) { + writer.write(' '); + } + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. + * + *

If indentFactor > 0 and the {@link JSONObject} + * has only one key, then the object will be output on a single line: + *

{@code {"key": 1}}
+ * + *

If an object has 2 or more keys, then it will be output across + * multiple lines:

{
+     *  "key1": 1,
+     *  "key2": "value 2",
+     *  "key3": 3
+     * }
+ *

+ * Warning: This method assumes that the data structure is acyclical. + * + * + * @param writer + * Writes the serialized JSON + * @param indentFactor + * The number of spaces to add to each level of indentation. + * @param indent + * The indentation of the top level. + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer, int indentFactor, int indent) + throws JSONException { + try { + boolean commanate = false; + final int length = this.length(); + writer.write('{'); + + if (length == 1) { + final Entry entry = this.entrySet().iterator().next(); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try{ + writeValue(writer, entry.getValue(), indentFactor, indent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + } else if (length != 0) { + final int newindent = indent + indentFactor; + for (final Entry entry : this.entrySet()) { + if (commanate) { + writer.write(','); + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, newindent); + final String key = entry.getKey(); + writer.write(quote(key)); + writer.write(':'); + if (indentFactor > 0) { + writer.write(' '); + } + try { + writeValue(writer, entry.getValue(), indentFactor, newindent); + } catch (Exception e) { + throw new JSONException("Unable to write JSONObject value for key: " + key, e); + } + commanate = true; + } + if (indentFactor > 0) { + writer.write('\n'); + } + indent(writer, indent); + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } + + /** + * Returns a java.util.Map containing all of the entries in this object. + * If an entry in the object is a JSONArray or JSONObject it will also + * be converted. + *

+ * Warning: This method assumes that the data structure is acyclical. + * + * @return a java.util.Map containing the entries of this object + */ + public Map toMap() { + Map results = new HashMap(); + for (Entry entry : this.entrySet()) { + Object value; + if (entry.getValue() == null || NULL.equals(entry.getValue())) { + value = null; + } else if (entry.getValue() instanceof JSONObject) { + value = ((JSONObject) entry.getValue()).toMap(); + } else if (entry.getValue() instanceof JSONArray) { + value = ((JSONArray) entry.getValue()).toList(); + } else { + value = entry.getValue(); + } + results.put(entry.getKey(), value); + } + return results; + } +} diff --git a/srcjar/org/json/JSONPointer.java b/srcjar/org/json/JSONPointer.java new file mode 100644 index 0000000..fc0b04b --- /dev/null +++ b/srcjar/org/json/JSONPointer.java @@ -0,0 +1,293 @@ +package org.json; + +import static java.lang.String.format; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/* +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 JSON Pointer is a simple query language defined for JSON documents by + * RFC 6901. + * + * In a nutshell, JSONPointer allows the user to navigate into a JSON document + * using strings, and retrieve targeted objects, like a simple form of XPATH. + * Path segments are separated by the '/' char, which signifies the root of + * the document when it appears as the first char of the string. Array + * elements are navigated using ordinals, counting from 0. JSONPointer strings + * may be extended to any arbitrary number of segments. If the navigation + * is successful, the matched item is returned. A matched item may be a + * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building + * fails, an appropriate exception is thrown. If the navigation fails to find + * a match, a JSONPointerException is thrown. + * + * @author JSON.org + * @version 2016-05-14 + */ +public class JSONPointer { + + // used for URL encoding and decoding + private static final String ENCODING = "utf-8"; + + /** + * This class allows the user to build a JSONPointer in steps, using + * exactly one segment in each step. + */ + public static class Builder { + + // Segments for the eventual JSONPointer string + private final List refTokens = new ArrayList(); + + /** + * Creates a {@code JSONPointer} instance using the tokens previously set using the + * {@link #append(String)} method calls. + */ + public JSONPointer build() { + return new JSONPointer(this.refTokens); + } + + /** + * Adds an arbitrary token to the list of reference tokens. It can be any non-null value. + * + * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the + * argument of this method MUST NOT be escaped. If you want to query the property called + * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no + * need to escape it as {@code "a~0b"}. + * + * @param token the new token to be appended to the list + * @return {@code this} + * @throws NullPointerException if {@code token} is null + */ + public Builder append(String token) { + if (token == null) { + throw new NullPointerException("token cannot be null"); + } + this.refTokens.add(token); + return this; + } + + /** + * Adds an integer to the reference token list. Although not necessarily, mostly this token will + * denote an array index. + * + * @param arrayIndex the array index to be added to the token list + * @return {@code this} + */ + public Builder append(int arrayIndex) { + this.refTokens.add(String.valueOf(arrayIndex)); + return this; + } + } + + /** + * Static factory method for {@link Builder}. Example usage: + * + *


+     * JSONPointer pointer = JSONPointer.builder()
+     *       .append("obj")
+     *       .append("other~key").append("another/key")
+     *       .append("\"")
+     *       .append(0)
+     *       .build();
+     * 
+ * + * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained + * {@link Builder#append(String)} calls. + */ + public static Builder builder() { + return new Builder(); + } + + // Segments for the JSONPointer string + private final List refTokens; + + /** + * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to + * evaluate the same JSON Pointer on different JSON documents then it is recommended + * to keep the {@code JSONPointer} instances due to performance considerations. + * + * @param pointer the JSON String or URI Fragment representation of the JSON pointer. + * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer + */ + public JSONPointer(final String pointer) { + if (pointer == null) { + throw new NullPointerException("pointer cannot be null"); + } + if (pointer.isEmpty() || pointer.equals("#")) { + this.refTokens = Collections.emptyList(); + return; + } + String refs; + if (pointer.startsWith("#/")) { + refs = pointer.substring(2); + try { + refs = URLDecoder.decode(refs, ENCODING); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } else if (pointer.startsWith("/")) { + refs = pointer.substring(1); + } else { + throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); + } + this.refTokens = new ArrayList(); + int slashIdx = -1; + int prevSlashIdx = 0; + do { + prevSlashIdx = slashIdx + 1; + slashIdx = refs.indexOf('/', prevSlashIdx); + if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) { + // found 2 slashes in a row ( obj//next ) + // or single slash at the end of a string ( obj/test/ ) + this.refTokens.add(""); + } else if (slashIdx >= 0) { + final String token = refs.substring(prevSlashIdx, slashIdx); + this.refTokens.add(unescape(token)); + } else { + // last item after separator, or no separator at all. + final String token = refs.substring(prevSlashIdx); + this.refTokens.add(unescape(token)); + } + } while (slashIdx >= 0); + // using split does not take into account consecutive separators or "ending nulls" + //for (String token : refs.split("/")) { + // this.refTokens.add(unescape(token)); + //} + } + + public JSONPointer(List refTokens) { + this.refTokens = new ArrayList(refTokens); + } + + private String unescape(String token) { + return token.replace("~1", "/").replace("~0", "~") + .replace("\\\"", "\"") + .replace("\\\\", "\\"); + } + + /** + * Evaluates this JSON Pointer on the given {@code document}. The {@code document} + * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty + * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the + * returned value will be {@code document} itself. + * + * @param document the JSON document which should be the subject of querying. + * @return the result of the evaluation + * @throws JSONPointerException if an error occurs during evaluation + */ + public Object queryFrom(Object document) throws JSONPointerException { + if (this.refTokens.isEmpty()) { + return document; + } + Object current = document; + for (String token : this.refTokens) { + if (current instanceof JSONObject) { + current = ((JSONObject) current).opt(unescape(token)); + } else if (current instanceof JSONArray) { + current = readByIndexToken(current, token); + } else { + throw new JSONPointerException(format( + "value [%s] is not an array or object therefore its key %s cannot be resolved", current, + token)); + } + } + return current; + } + + /** + * Matches a JSONArray element by ordinal position + * @param current the JSONArray to be evaluated + * @param indexToken the array index in string form + * @return the matched object. If no matching item is found a + * @throws JSONPointerException is thrown if the index is out of bounds + */ + private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException { + try { + int index = Integer.parseInt(indexToken); + JSONArray currentArr = (JSONArray) current; + if (index >= currentArr.length()) { + throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, + currentArr.length())); + } + try { + return currentArr.get(index); + } catch (JSONException e) { + throw new JSONPointerException("Error reading value at index position " + index, e); + } + } catch (NumberFormatException e) { + throw new JSONPointerException(format("%s is not an array index", indexToken), e); + } + } + + /** + * Returns a string representing the JSONPointer path value using string + * representation + */ + @Override + public String toString() { + StringBuilder rval = new StringBuilder(""); + for (String token: this.refTokens) { + rval.append('/').append(escape(token)); + } + return rval.toString(); + } + + /** + * Escapes path segment values to an unambiguous form. + * The escape char to be inserted is '~'. The chars to be escaped + * are ~, which maps to ~0, and /, which maps to ~1. Backslashes + * and double quote chars are also escaped. + * @param token the JSONPointer segment value to be escaped + * @return the escaped value for the token + */ + private String escape(String token) { + return token.replace("~", "~0") + .replace("/", "~1") + .replace("\\", "\\\\") + .replace("\"", "\\\""); + } + + /** + * Returns a string representing the JSONPointer path value using URI + * fragment identifier representation + */ + public String toURIFragment() { + try { + StringBuilder rval = new StringBuilder("#"); + for (String token : this.refTokens) { + rval.append('/').append(URLEncoder.encode(token, ENCODING)); + } + return rval.toString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/srcjar/org/json/JSONPointerException.java b/srcjar/org/json/JSONPointerException.java new file mode 100644 index 0000000..0ce1aeb --- /dev/null +++ b/srcjar/org/json/JSONPointerException.java @@ -0,0 +1,45 @@ +package org.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. +*/ + +/** + * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs + * during evaluating a pointer. + * + * @author JSON.org + * @version 2016-05-13 + */ +public class JSONPointerException extends JSONException { + private static final long serialVersionUID = 8872944667561856751L; + + public JSONPointerException(String message) { + super(message); + } + + public JSONPointerException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/srcjar/org/json/JSONPropertyIgnore.java b/srcjar/org/json/JSONPropertyIgnore.java new file mode 100644 index 0000000..682de74 --- /dev/null +++ b/srcjar/org/json/JSONPropertyIgnore.java @@ -0,0 +1,43 @@ +package org.json; + +/* +Copyright (c) 2018 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 static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({METHOD}) +/** + * Use this annotation on a getter method to override the Bean name + * parser for Bean -> JSONObject mapping. If this annotation is + * present at any level in the class hierarchy, then the method will + * not be serialized from the bean into the JSONObject. + */ +public @interface JSONPropertyIgnore { } diff --git a/srcjar/org/json/JSONPropertyName.java b/srcjar/org/json/JSONPropertyName.java new file mode 100644 index 0000000..a1bcd58 --- /dev/null +++ b/srcjar/org/json/JSONPropertyName.java @@ -0,0 +1,47 @@ +package org.json; + +/* +Copyright (c) 2018 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 static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Documented +@Retention(RUNTIME) +@Target({METHOD}) +/** + * Use this annotation on a getter method to override the Bean name + * parser for Bean -> JSONObject mapping. A value set to empty string "" + * will have the Bean parser fall back to the default field name processing. + */ +public @interface JSONPropertyName { + /** + * @return The name of the property as to be used in the JSON Object. + */ + String value(); +} diff --git a/srcjar/org/json/JSONString.java b/srcjar/org/json/JSONString.java new file mode 100644 index 0000000..1f2d77d --- /dev/null +++ b/srcjar/org/json/JSONString.java @@ -0,0 +1,18 @@ +package org.json; +/** + * The JSONString interface allows a toJSONString() + * method so that a class can change the behavior of + * JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The + * toJSONString method will be used instead of the default behavior + * of using the Object's toString() method and quoting the result. + */ +public interface JSONString { + /** + * The toJSONString method allows a class to produce its own JSON + * serialization. + * + * @return A strictly syntactically correct JSON text. + */ + public String toJSONString(); +} diff --git a/srcjar/org/json/JSONStringer.java b/srcjar/org/json/JSONStringer.java new file mode 100644 index 0000000..bb9e7a4 --- /dev/null +++ b/srcjar/org/json/JSONStringer.java @@ -0,0 +1,79 @@ +package org.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. + *

+ * A JSONStringer instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting cascade style. For example,

+ * myString = new JSONStringer()
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject()
+ *     .toString();
which produces the string
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * 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. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2015-12-09 + */ +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 null if there was a + * problem in the construction of the JSON text (such as the calls to + * array were not properly balanced with calls to + * endArray). + * @return The JSON text. + */ + @Override + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } +} diff --git a/srcjar/org/json/JSONTokener.java b/srcjar/org/json/JSONTokener.java new file mode 100644 index 0000000..36bce45 --- /dev/null +++ b/srcjar/org/json/JSONTokener.java @@ -0,0 +1,529 @@ +package org.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: + +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 2014-05-03 + */ +public class JSONTokener { + /** current read character position on the current line. */ + private long character; + /** flag to indicate if the end of the input has been found. */ + private boolean eof; + /** current read index of the input. */ + private long index; + /** current line of the input. */ + private long line; + /** previous character read from the input. */ + private char previous; + /** Reader for the input. */ + private final Reader reader; + /** flag to indicate that a previous character was requested. */ + private boolean usePrevious; + /** the number of characters read in the previous line. */ + private long characterPreviousLine; + + + /** + * Construct a JSONTokener from a Reader. The caller must close the Reader. + * + * @param reader A reader. + */ + 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.characterPreviousLine = 0; + this.line = 1; + } + + + /** + * Construct a JSONTokener from an InputStream. The caller must close the input stream. + * @param inputStream The source. + */ + public JSONTokener(InputStream inputStream) { + this(new InputStreamReader(inputStream)); + } + + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + + /** + * 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. + * @throws JSONException Thrown if trying to step back more than 1 step + * or if already at the start of the string + */ + public void back() throws JSONException { + if (this.usePrevious || this.index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.decrementIndexes(); + this.usePrevious = true; + this.eof = false; + } + + /** + * Decrements the indexes for the {@link #back()} method based on the previous character read. + */ + private void decrementIndexes() { + this.index--; + if(this.previous=='\r' || this.previous == '\n') { + this.line--; + this.character=this.characterPreviousLine ; + } else if(this.character > 0){ + this.character--; + } + } + + /** + * 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); + } + return -1; + } + + /** + * Checks if the end of the input has been reached. + * + * @return true if at the end of the file and we didn't step back + */ + public boolean end() { + return this.eof && !this.usePrevious; + } + + + /** + * Determine if the source string still contains characters that next() + * can consume. + * @return true if not yet at the end of the source. + * @throws JSONException thrown if there is an error stepping forward + * or backward while checking for more data. + */ + public boolean more() throws JSONException { + if(this.usePrevious) { + return true; + } + try { + this.reader.mark(1); + } catch (IOException e) { + throw new JSONException("Unable to preserve stream position", e); + } + try { + // -1 is EOF, but next() can not consume the null character '\0' + if(this.reader.read() <= 0) { + this.eof = true; + return false; + } + this.reader.reset(); + } catch (IOException e) { + throw new JSONException("Unable to read the next character from the stream", e); + } + return true; + } + + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + * @throws JSONException Thrown if there is an error reading the source string. + */ + 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); + } + } + if (c <= 0) { // End of stream + this.eof = true; + return 0; + } + this.incrementIndexes(c); + this.previous = (char) c; + return this.previous; + } + + /** + * Increments the internal indexes according to the previous character + * read and the character passed as the current character. + * @param c the current character read. + */ + private void incrementIndexes(int c) { + if(c > 0) { + this.index++; + if(c=='\r') { + this.line++; + this.characterPreviousLine = this.character; + this.character=0; + }else if (c=='\n') { + if(this.previous != '\r') { + this.line++; + this.characterPreviousLine = this.character; + } + this.character=0; + } else { + this.character++; + } + } + } + + /** + * 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. + */ + public char next(char c) throws JSONException { + char n = this.next(); + if (n != c) { + if(n > 0) { + throw this.syntaxError("Expected '" + c + "' and instead saw '" + + n + "'"); + } + throw this.syntaxError("Expected '" + c + "' and instead saw ''"); + } + 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. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; + } + + char[] chars = new char[n]; + int pos = 0; + + while (pos < n) { + chars[pos] = this.next(); + if (this.end()) { + throw this.syntaxError("Substring bounds error"); + } + pos += 1; + } + return new String(chars); + } + + + /** + * Get the next char in the string, skipping whitespace. + * @throws JSONException Thrown if there is an error reading the source string. + * @return A character, or 0 if there are no more characters. + */ + public char nextClean() throws JSONException { + for (;;) { + char c = this.next(); + 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 + * " (double quote) or + * ' (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + 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': + try { + sb.append((char)Integer.parseInt(this.next(4), 16)); + } catch (NumberFormatException e) { + throw this.syntaxError("Illegal escape.", e); + } + break; + case '"': + case '\'': + case '\\': + case '/': + sb.append(c); + break; + default: + throw this.syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } + + + /** + * 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. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(char delimiter) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = this.next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + this.back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + + /** + * 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. + * @throws JSONException Thrown if there is an error while searching + * for the delimiter + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuilder sb = new StringBuilder(); + 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); + } + } + + + /** + * 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. + * + * @return An object. + */ + 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); + } + + /* + * 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. + */ + + StringBuilder sb = new StringBuilder(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = this.next(); + } + this.back(); + + string = sb.toString().trim(); + if ("".equals(string)) { + throw this.syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + + /** + * 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. + * @throws JSONException Thrown if there is an error while searching + * for the to character + */ + 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) { + // in some readers, reset() may throw an exception if + // the remaining portion of the input is greater than + // the mark size (1,000,000 above). + this.reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return 0; + } + } while (c != to); + this.reader.mark(1); + } catch (IOException exception) { + throw new JSONException(exception); + } + this.back(); + return c; + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + this.toString()); + } + + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @param causedBy The throwable that caused the error. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message, Throwable causedBy) { + return new JSONException(message + this.toString(), causedBy); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + @Override + public String toString() { + return " at " + this.index + " [character " + this.character + " line " + + this.line + "]"; + } +} diff --git a/srcjar/org/json/JSONWriter.java b/srcjar/org/json/JSONWriter.java new file mode 100644 index 0000000..f520c0c --- /dev/null +++ b/srcjar/org/json/JSONWriter.java @@ -0,0 +1,418 @@ +package org.json; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Map; + +/* +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. + *

+ * A JSONWriter instance provides a value method for appending + * values to the + * text, and a key + * method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and + * object and endObject methods which make and bound + * object values. All of these methods return the JSONWriter instance, + * permitting a cascade style. For example,

+ * new JSONWriter(myWriter)
+ *     .object()
+ *         .key("JSON")
+ *         .value("Hello, World!")
+ *     .endObject();
which writes
+ * {"JSON":"Hello, World!"}
+ *

+ * The first method called must be array or object. + * There are no methods for adding commas or colons. JSONWriter adds them for + * you. Objects and arrays can be nested up to 200 levels deep. + *

+ * This can sometimes be easier than using a JSONObject to build a string. + * @author JSON.org + * @version 2016-08-08 + */ +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 Appendable writer; + + /** + * Make a fresh JSONWriter. It can be used to build one JSON text. + */ + public JSONWriter(Appendable 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.append(','); + } + this.writer.append(string); + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + 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 + * endArray will be appended to this array. The + * endArray 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 m Mode + * @param c Closing character + * @return this + * @throws JSONException If unbalanced. + */ + private JSONWriter end(char m, char c) throws JSONException { + if (this.mode != m) { + throw new JSONException(m == 'a' + ? "Misplaced endArray." + : "Misplaced endObject."); + } + this.pop(m); + try { + this.writer.append(c); + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to + * array. + * @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 + * object. + * @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 { + JSONObject topObject = this.stack[this.top - 1]; + // don't use the built in putOnce method to maintain Android support + if(topObject.has(string)) { + throw new JSONException("Duplicate key \"" + string + "\""); + } + topObject.put(string, true); + if (this.comma) { + this.writer.append(','); + } + this.writer.append(JSONObject.quote(string)); + this.writer.append(':'); + this.comma = false; + this.mode = 'o'; + return this; + } catch (IOException e) { + // Android as of API 25 does not support this exception constructor + // however we won't worry about it. If an exception is happening here + // it will just throw a "Method not found" exception instead. + throw new JSONException(e); + } + } + throw new JSONException("Misplaced key."); + } + + + /** + * Begin appending a new object. All keys and values until the balancing + * endObject will be appended to this object. The + * endObject 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 jo 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; + } + + /** + * 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 other means. If the value + * is an array or Collection, then a JSONArray will be made from it and its + * toJSONString method will be called. If the value is a MAP, then a + * JSONObject will be made from it and its toJSONString method will be + * called. Otherwise, the value's toString method will be called, and the + * result will be quoted. + * + *

+ * 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 { (left + * brace) and ending with } (right + * brace). + * @throws JSONException + * If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + // not all Numbers may match actual JSON Numbers. i.e. Fractions or Complex + final String numberAsString = JSONObject.numberToString((Number) value); + try { + // Use the BigDecimal constructor for it's parser to validate the format. + @SuppressWarnings("unused") + BigDecimal unused = new BigDecimal(numberAsString); + // Close enough to a JSON number that we will return it unquoted + return numberAsString; + } catch (NumberFormatException ex){ + // The Number value is not a valid JSON number. + // Instead we will quote it as a string + return JSONObject.quote(numberAsString); + } + } + if (value instanceof Boolean || value instanceof JSONObject + || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + Map map = (Map) value; + return new JSONObject(map).toString(); + } + if (value instanceof Collection) { + Collection coll = (Collection) value; + return new JSONArray(coll).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + if(value instanceof Enum){ + return JSONObject.quote(((Enum)value).name()); + } + return JSONObject.quote(value.toString()); + } + + /** + * Append either the value true or the value + * false. + * @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(Double.valueOf(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(valueToString(object)); + } +} diff --git a/srcjar/org/json/LICENSE b/srcjar/org/json/LICENSE new file mode 100644 index 0000000..02ee0ef --- /dev/null +++ b/srcjar/org/json/LICENSE @@ -0,0 +1,23 @@ +============================================================================ + +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. diff --git a/srcjar/org/json/Property.java b/srcjar/org/json/Property.java new file mode 100644 index 0000000..ff33a04 --- /dev/null +++ b/srcjar/org/json/Property.java @@ -0,0 +1,75 @@ +package org.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.Enumeration; +import java.util.Properties; + +/** + * Converts a Property file data into JSONObject and back. + * @author JSON.org + * @version 2015-05-05 + */ +public class Property { + /** + * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. + * @param properties java.util.Properties + * @return JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { + // can't use the new constructor for Android support + // JSONObject jo = new JSONObject(properties == null ? 0 : properties.size()); + JSONObject jo = new JSONObject(); + if (properties != null && !properties.isEmpty()) { + Enumeration enumProperties = properties.propertyNames(); + while(enumProperties.hasMoreElements()) { + String name = (String)enumProperties.nextElement(); + jo.put(name, properties.getProperty(name)); + } + } + return jo; + } + + /** + * Converts the JSONObject into a property file object. + * @param jo JSONObject + * @return java.util.Properties + * @throws JSONException + */ + public static Properties toProperties(JSONObject jo) throws JSONException { + Properties properties = new Properties(); + if (jo != null) { + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { + Object value = jo.opt(key); + if (!JSONObject.NULL.equals(value)) { + properties.put(key, value.toString()); + } + } + } + return properties; + } +} diff --git a/srcjar/org/json/README.md b/srcjar/org/json/README.md new file mode 100644 index 0000000..82ffedc --- /dev/null +++ b/srcjar/org/json/README.md @@ -0,0 +1,136 @@ +JSON in Java [package org.json] +=============================== + +[![Maven Central](https://img.shields.io/maven-central/v/org.json/json.svg)](https://mvnrepository.com/artifact/org.json/json) + +JSON is a light-weight, language independent, data interchange format. +See http://www.JSON.org/ + +The files in this package implement JSON encoders/decoders in Java. +It also includes the capability to convert between JSON and XML, HTTP +headers, Cookies, and CDL. + +This is a reference implementation. There is a large number of JSON packages +in Java. Perhaps someday the Java community will standardize on one. Until +then, choose carefully. + +The license includes this restriction: "The software shall be used for good, +not evil." If your conscience cannot live with that, then choose a different +package. + +The package compiles on Java 1.6-1.8. + + +**JSONObject.java**: The `JSONObject` can parse text from a `String` or a `JSONTokener` +to produce a map-like object. The object provides methods for manipulating its +contents, and for producing a JSON compliant object serialization. + +**JSONArray.java**: The `JSONArray` can parse text from a String or a `JSONTokener` +to produce a vector-like object. The object provides methods for manipulating +its contents, and for producing a JSON compliant array serialization. + +**JSONTokener.java**: The `JSONTokener` breaks a text into a sequence of individual +tokens. It can be constructed from a `String`, `Reader`, or `InputStream`. + +**JSONException.java**: The `JSONException` is the standard exception type thrown +by this package. + +**JSONPointer.java**: Implementation of +[JSON Pointer (RFC 6901)](https://tools.ietf.org/html/rfc6901). Supports +JSON Pointers both in the form of string representation and URI fragment +representation. + +**JSONPropertyIgnore.java**: Annotation class that can be used on Java Bean getter methods. +When used on a bean method that would normally be serialized into a `JSONObject`, it +overrides the getter-to-key-name logic and forces the property to be excluded from the +resulting `JSONObject`. + +**JSONPropertyName.java**: Annotation class that can be used on Java Bean getter methods. +When used on a bean method that would normally be serialized into a `JSONObject`, it +overrides the getter-to-key-name logic and uses the value of the annotation. The Bean +processor will look through the class hierarchy. This means you can use the annotation on +a base class or interface and the value of the annotation will be used even if the getter +is overridden in a child class. + +**JSONString.java**: The `JSONString` interface requires a `toJSONString` method, +allowing an object to provide its own serialization. + +**JSONStringer.java**: The `JSONStringer` provides a convenient facility for +building JSON strings. + +**JSONWriter.java**: The `JSONWriter` provides a convenient facility for building +JSON text through a writer. + + +**CDL.java**: `CDL` provides support for converting between JSON and comma +delimited lists. + +**Cookie.java**: `Cookie` provides support for converting between JSON and cookies. + +**CookieList.java**: `CookieList` provides support for converting between JSON and +cookie lists. + +**HTTP.java**: `HTTP` provides support for converting between JSON and HTTP headers. + +**HTTPTokener.java**: `HTTPTokener` extends `JSONTokener` for parsing HTTP headers. + +**XML.java**: `XML` provides support for converting between JSON and XML. + +**JSONML.java**: `JSONML` provides support for converting between JSONML and XML. + +**XMLTokener.java**: `XMLTokener` extends `JSONTokener` for parsing XML text. + +Unit tests are maintained in a separate project. Contributing developers can test +JSON-java pull requests with the code in this project: +https://github.com/stleary/JSON-Java-unit-test + +Numeric types in this package comply with +[ECMA-404: The JSON Data Interchange Format](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf) and +[RFC 7159: The JavaScript Object Notation (JSON) Data Interchange Format](https://tools.ietf.org/html/rfc7159#section-6). +This package fully supports `Integer`, `Long`, and `Double` Java types. Partial support +for `BigInteger` and `BigDecimal` values in `JSONObject` and `JSONArray` objects is provided +in the form of `get()`, `opt()`, and `put()` API methods. + +Although 1.6 compatibility is currently supported, it is not a project goal and may be +removed in some future release. + +In compliance with RFC7159 page 10 section 9, the parser is more lax with what is valid +JSON than the Generator. For Example, the tab character (U+0009) is allowed when reading +JSON Text strings, but when output by the Generator, tab is properly converted to \t in +the string. Other instances may occur where reading invalid JSON text does not cause an +error to be generated. Malformed JSON Texts such as missing end " (quote) on strings or +invalid number formats (1.2e6.3) will cause errors as such documents can not be read + reliably. + +Release history: + +~~~ +20180813 POM change to include Automatic-Module-Name (#431) + +20180130 Recent commits + +20171018 Checkpoint for recent commits. + +20170516 Roll up recent commits. + +20160810 Revert code that was breaking opt*() methods. + +20160807 This release contains a bug in the JSONObject.opt*() and JSONArray.opt*() methods, +it is not recommended for use. +Java 1.6 compatability fixed, JSONArray.toList() and JSONObject.toMap(), +RFC4180 compatibility, JSONPointer, some exception fixes, optional XML type conversion. +Contains the latest code as of 7 Aug, 2016 + +20160212 Java 1.6 compatibility, OSGi bundle. Contains the latest code as of 12 Feb, 2016. + +20151123 JSONObject and JSONArray initialization with generics. Contains the +latest code as of 23 Nov, 2015. + +20150729 Checkpoint for Maven central repository release. Contains the latest code +as of 29 July, 2015. +~~~ + + +JSON-java releases can be found by searching the Maven repository for groupId "org.json" +and artifactId "json". For example: +https://search.maven.org/search?q=g:org.json%20AND%20a:json&core=gav diff --git a/srcjar/org/json/XML.java b/srcjar/org/json/XML.java new file mode 100644 index 0000000..55362b2 --- /dev/null +++ b/srcjar/org/json/XML.java @@ -0,0 +1,683 @@ +package org.json; + +/* +Copyright (c) 2015 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.Reader; +import java.io.StringReader; +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 2016-08-10 + */ +@SuppressWarnings("boxing") +public class XML { + /** The Character '&'. */ + public static final Character AMP = '&'; + + /** The Character '''. */ + public static final Character APOS = '\''; + + /** The Character '!'. */ + public static final Character BANG = '!'; + + /** The Character '='. */ + public static final Character EQ = '='; + + /** The Character '>'. */ + public static final Character GT = '>'; + + /** The Character '<'. */ + public static final Character LT = '<'; + + /** The Character '?'. */ + public static final Character QUEST = '?'; + + /** The Character '"'. */ + public static final Character QUOT = '"'; + + /** The Character '/'. */ + public static final Character SLASH = '/'; + + /** + * Creates an iterator for navigating Code Points in a string instead of + * characters. Once Java7 support is dropped, this can be replaced with + * + * string.codePoints() + * + * which is available in Java8 and above. + * + * @see http://stackoverflow.com/a/21791059/6030888 + */ + private static Iterable codePointIterator(final String string) { + return new Iterable() { + @Override + public Iterator iterator() { + return new Iterator() { + private int nextIndex = 0; + private int length = string.length(); + + @Override + public boolean hasNext() { + return this.nextIndex < this.length; + } + + @Override + public Integer next() { + int result = string.codePointAt(this.nextIndex); + this.nextIndex += Character.charCount(result); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + /** + * Replace special characters with XML escapes: + * + *

+     * & (ampersand) is replaced by &amp;
+     * < (less than) is replaced by &lt;
+     * > (greater than) is replaced by &gt;
+     * " (double quote) is replaced by &quot;
+     * ' (single quote / apostrophe) is replaced by &apos;
+     * 
+ * + * @param string + * The string to be escaped. + * @return The escaped string. + */ + public static String escape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (final int cp : codePointIterator(string)) { + switch (cp) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + if (mustEscape(cp)) { + sb.append("&#x"); + sb.append(Integer.toHexString(cp)); + sb.append(';'); + } else { + sb.appendCodePoint(cp); + } + } + } + return sb.toString(); + } + + /** + * @param cp code point to test + * @return true if the code point is not valid for an XML + */ + private static boolean mustEscape(int cp) { + /* Valid range from https://www.w3.org/TR/REC-xml/#charsets + * + * #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + * + * any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. + */ + // isISOControl is true when (cp >= 0 && cp <= 0x1F) || (cp >= 0x7F && cp <= 0x9F) + // all ISO control characters are out of range except tabs and new lines + return (Character.isISOControl(cp) + && cp != 0x9 + && cp != 0xA + && cp != 0xD + ) || !( + // valid the range of acceptable characters that aren't control + (cp >= 0x20 && cp <= 0xD7FF) + || (cp >= 0xE000 && cp <= 0xFFFD) + || (cp >= 0x10000 && cp <= 0x10FFFF) + ) + ; + } + + /** + * Removes XML escapes from the string. + * + * @param string + * string to remove escapes from + * @return string with converted entities + */ + public static String unescape(String string) { + StringBuilder sb = new StringBuilder(string.length()); + for (int i = 0, length = string.length(); i < length; i++) { + char c = string.charAt(i); + if (c == '&') { + final int semic = string.indexOf(';', i); + if (semic > i) { + final String entity = string.substring(i + 1, semic); + sb.append(XMLTokener.unescapeEntity(entity)); + // skip past the entity we just parsed. + i += entity.length() + 1; + } else { + // this shouldn't happen in most cases since the parser + // errors on unclosed entries. + sb.append(c); + } + } else { + // not part of an entity + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. Whitespace is not + * allowed in tagNames and attributes. + * + * @param string + * A string. + * @throws JSONException Thrown if the string contains whitespace or is empty. + */ + 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, boolean keepStrings) + 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(); + + // "); + 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 ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.length() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + + } else if (token == GT) { + // Content, between <...> and + 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", + keepStrings ? string : stringToValue(string)); + } + + } else if (token == LT) { + // Nested element + if (parse(x, jsonobject, tagName,keepStrings)) { + 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"); + } + } + } + } + + /** + * This method is the same as {@link JSONObject#stringToValue(String)}. + * + * @param string String to convert + * @return JSON value of this string or the string + */ + // To maintain compatibility with the Android API, this method is a direct copy of + // the one in JSONObject. Changes made here should be reflected there. + public static Object stringToValue(String string) { + if (string.equals("")) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + + /* + * If it might be a number, try converting it. If a number cannot be + * produced, then the value will just be a string. + */ + + char initial = string.charAt(0); + if ((initial >= '0' && initial <= '9') || initial == '-') { + try { + // if we want full Big Number support this block can be replaced with: + // return stringToNumber(string); + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 + || string.indexOf('E') > -1 || "-0".equals(string)) { + Double d = Double.valueOf(string); + if (!d.isInfinite() && !d.isNaN()) { + return d; + } + } else { + Long myLong = Long.valueOf(string); + if (string.equals(myLong.toString())) { + if (myLong.longValue() == myLong.intValue()) { + return Integer.valueOf(myLong.intValue()); + } + 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 <[ [ ]]> + * are ignored. + * + * @param string + * The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(string, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML 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 <[ [ ]]> + * are ignored. + * + * @param reader The XML source reader. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader) throws JSONException { + return toJSONObject(reader, false); + } + + /** + * Convert a well-formed (but not necessarily valid) XML 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 <[ [ ]]> + * are ignored. + * + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to + * numbers but will instead be the exact value as seen in the XML document. + * + * @param reader The XML source reader. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(Reader reader, boolean keepStrings) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(reader); + while (x.more()) { + x.skipPast("<"); + if(x.more()) { + parse(x, jo, null, keepStrings); + } + } + return jo; + } + + /** + * 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 <[ [ ]]> + * are ignored. + * + * All values are converted as strings, for 1, 01, 29.0 will not be coerced to + * numbers but will instead be the exact value as seen in the XML document. + * + * @param string + * The source string. + * @param keepStrings If true, then values will not be coerced into boolean + * or numeric values and will instead be left as strings + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException Thrown if there is an errors while parsing the string + */ + public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { + return toJSONObject(new StringReader(string), keepStrings); + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object + * A JSONObject. + * @return A string. + * @throws JSONException Thrown if there is an error parsing the string + */ + 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 Thrown if there is an error parsing the string + */ + public static String toString(final Object object, final String tagName) + throws JSONException { + StringBuilder sb = new StringBuilder(); + JSONArray ja; + JSONObject jo; + String string; + + if (object instanceof JSONObject) { + + // Emit + if (tagName != null) { + sb.append('<'); + sb.append(tagName); + sb.append('>'); + } + + // Loop thru the keys. + // don't use the new entrySet accessor to maintain Android Support + jo = (JSONObject) object; + for (final String key : jo.keySet()) { + Object value = jo.opt(key); + if (value == null) { + value = ""; + } else if (value.getClass().isArray()) { + value = new JSONArray(value); + } + + // Emit content in body + if ("content".equals(key)) { + if (value instanceof JSONArray) { + ja = (JSONArray) value; + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + if (i > 0) { + sb.append('\n'); + } + Object val = ja.opt(i); + sb.append(escape(val.toString())); + } + } else { + sb.append(escape(value.toString())); + } + + // Emit an array of similar keys + + } else if (value instanceof JSONArray) { + ja = (JSONArray) value; + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + Object val = ja.opt(i); + if (val instanceof JSONArray) { + sb.append('<'); + sb.append(key); + sb.append('>'); + sb.append(toString(val)); + sb.append("'); + } else { + sb.append(toString(val, key)); + } + } + } else if ("".equals(value)) { + sb.append('<'); + sb.append(key); + sb.append("/>"); + + // Emit a new tag + + } else { + sb.append(toString(value, key)); + } + } + if (tagName != null) { + + // Emit the close tag + sb.append("'); + } + return sb.toString(); + + } + + if (object != null && (object instanceof JSONArray || object.getClass().isArray())) { + if(object.getClass().isArray()) { + ja = new JSONArray(object); + } else { + ja = (JSONArray) object; + } + int jaLength = ja.length(); + // don't use the new iterator API to maintain support for Android + for (int i = 0; i < jaLength; i++) { + Object val = ja.opt(i); + // XML does not have good support for arrays. If an array + // appears in a place where XML is lacking, synthesize an + // element. + sb.append(toString(val, tagName == null ? "array" : tagName)); + } + return sb.toString(); + } + + string = (object == null) ? "null" : escape(object.toString()); + return (tagName == null) ? "\"" + string + "\"" + : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName + + ">" + string + ""; + + } +} diff --git a/srcjar/org/json/XMLTokener.java b/srcjar/org/json/XMLTokener.java new file mode 100644 index 0000000..50e3acc --- /dev/null +++ b/srcjar/org/json/XMLTokener.java @@ -0,0 +1,407 @@ +package org.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.Reader; + +/** + * The XMLTokener extends the JSONTokener to provide additional methods + * for the parsing of XML texts. + * @author JSON.org + * @version 2015-12-09 + */ +public class XMLTokener extends JSONTokener { + + + /** The table of entity values. It initially contains Character values for + * amp, apos, gt, lt, quot. + */ + public static final java.util.HashMap entity; + + static { + entity = new java.util.HashMap(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } + + /** + * Construct an XMLTokener from a Reader. + * @param r A source reader. + */ + public XMLTokener(Reader r) { + super(r); + } + + /** + * Construct an XMLTokener from a string. + * @param s A source string. + */ + public XMLTokener(String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + StringBuilder sb = new StringBuilder(); + while (more()) { + c = next(); + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' && + sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } + } + throw syntaxError("Unclosed CDATA"); + } + + + /** + * Get the next XML outer token, trimming whitespace. There are two kinds + * of tokens: the '<' character which begins a markup tag, and the content + * text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more + * source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; + } + if (c == '<') { + return XML.LT; + } + sb = new StringBuilder(); + for (;;) { + if (c == 0) { + return sb.toString().trim(); + } + if (c == '<') { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } + + + /** + * Return the next entity. These entities are translated to Characters: + * & ' > < ". + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char ampersand) throws JSONException { + StringBuilder sb = new StringBuilder(); + for (;;) { + char c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + String string = sb.toString(); + return unescapeEntity(string); + } + + /** + * Unescapes an XML entity encoding; + * @param e entity (only the actual entity value, not the preceding & or ending ; + * @return + */ + static String unescapeEntity(String e) { + // validate + if (e == null || e.isEmpty()) { + return ""; + } + // if our entity is an encoded unicode point, parse it. + if (e.charAt(0) == '#') { + int cp; + if (e.charAt(1) == 'x') { + // hex encoded unicode + cp = Integer.parseInt(e.substring(2), 16); + } else { + // decimal encoded unicode + cp = Integer.parseInt(e.substring(1)); + } + return new String(new int[] {cp},0,1); + } + Character knownEntity = entity.get(e); + if(knownEntity==null) { + // we don't know the entity so keep it encoded + return '&' + e + ';'; + } + return knownEntity.toString(); + } + + + /** + * Returns the next XML meta token. This is used for skipping over + * and structures. + * @return Syntax characters (< > / = ! ?) are returned as + * Character, and strings and names are returned as Boolean. We don't care + * what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML + * is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (;;) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } + } + } + } + + + /** + * Get the next XML Token. These tokens are found inside of angle + * brackets. It may be one of these characters: / > = ! ? or it + * may be a string wrapped in single quotes or double quotes, or it may be a + * name. + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuilder sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + +// Quoted string + + case '"': + case '\'': + q = c; + sb = new StringBuilder(); + for (;;) { + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + } + default: + +// Name + + sb = new StringBuilder(); + for (;;) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { + case 0: + return sb.toString(); + case '>': + case '/': + case '=': + case '!': + case '?': + case '[': + case ']': + back(); + return sb.toString(); + case '<': + case '"': + case '\'': + throw syntaxError("Bad character in a name"); + } + } + } + } + + + /** + * Skip characters until past the requested string. + * If it is not found, we are left at the end of the source with a result of false. + * @param to A string to skip past. + */ + // The Android implementation of JSONTokener has a public method of public void skipPast(String to) + // even though ours does not have that method, to have API compatibility, our method in the subclass + // should match. + public void skipPast(String to) { + boolean b; + char c; + int i; + int j; + int offset = 0; + int length = to.length(); + char[] circle = new char[length]; + + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. + */ + + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return; + } + circle[i] = c; + } + + /* We will loop, possibly for all of the remaining characters. */ + + for (;;) { + j = offset; + b = true; + + /* Compare the circle buffer with the to string. */ + + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; + } + j += 1; + if (j >= length) { + j -= length; + } + } + + /* If we exit the loop with b intact, then victory is ours. */ + + if (b) { + return; + } + + /* Get the next character. If there isn't one, then defeat is ours. */ + + c = next(); + if (c == 0) { + return; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } + } + } +} -- 1.7.10.2