From e8b1b66888f95341b629aa6667b2820823a1ca2f Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Wed, 21 Jun 2017 11:52:15 -0400 Subject: [PATCH] Updates for supporting the Android API --- CookieList.java | 14 +++---- HTTP.java | 8 ++-- JSONML.java | 14 +++---- JSONObject.java | 56 ++++------------------------ JSONPointer.java | 17 ++++++--- JSONWriter.java | 96 +++++++++++++++++++++++++++++++++++++++++++++++- Property.java | 8 ++-- XML.java | 77 ++++++++++++++++++++++++++++++-------- XMLTokener.java | 9 ++--- 9 files changed, 198 insertions(+), 101 deletions(-) diff --git a/CookieList.java b/CookieList.java index 8cb4e5e..c67ee3a 100644 --- a/CookieList.java +++ b/CookieList.java @@ -1,7 +1,5 @@ package org.json; -import java.util.Map.Entry; - /* Copyright (c) 2002 JSON.org @@ -24,7 +22,7 @@ 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. @@ -69,17 +67,17 @@ public class CookieList { */ public static String toString(JSONObject jo) throws JSONException { boolean b = false; - StringBuilder sb = new StringBuilder(); - for (final Entry entry : jo.entrySet()) { - final String key = entry.getKey(); - final Object value = entry.getValue(); + 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())); + sb.append(Cookie.escape(value.toString())); b = true; } } diff --git a/HTTP.java b/HTTP.java index 22635ff..70b88ee 100644 --- a/HTTP.java +++ b/HTTP.java @@ -25,7 +25,6 @@ SOFTWARE. */ import java.util.Locale; -import java.util.Map.Entry; /** * Convert an HTTP header to a JSONObject and back. @@ -145,11 +144,12 @@ public class HTTP { throw new JSONException("Not enough material for an HTTP header."); } sb.append(CRLF); - for (final Entry entry : jo.entrySet()) { - final String key = entry.getKey(); + // 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(entry.getValue())) { + !"Request-URI".equals(key) && !JSONObject.NULL.equals(value)) { sb.append(key); sb.append(": "); sb.append(jo.optString(key)); diff --git a/JSONML.java b/JSONML.java index c1d50b3..2dcbd25 100644 --- a/JSONML.java +++ b/JSONML.java @@ -1,7 +1,5 @@ package org.json; -import java.util.Map.Entry; - /* Copyright (c) 2008 JSON.org @@ -416,10 +414,10 @@ public class JSONML { // Emit the attributes - for (final Entry entry : jo.entrySet()) { - final String key = entry.getKey(); + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { + final Object value = jo.opt(key); XML.noSpace(key); - final Object value = entry.getValue(); if (value != null) { sb.append(' '); sb.append(XML.escape(key)); @@ -495,11 +493,11 @@ public class JSONML { //Emit the attributes - for (final Entry entry : jo.entrySet()) { - final String key = entry.getKey(); + // Don't use the new entrySet API to maintain Android support + for (final String key : jo.keySet()) { if (!"tagName".equals(key) && !"childNodes".equals(key)) { XML.noSpace(key); - value = entry.getValue(); + value = jo.opt(key); if (value != null) { sb.append(' '); sb.append(XML.escape(key)); diff --git a/JSONObject.java b/JSONObject.java index 8ad7864..89c94f4 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -1908,6 +1908,8 @@ public class JSONObject { * 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; @@ -2065,55 +2067,11 @@ public class JSONObject { * 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 = 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 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 quote(((Enum)value).name()); - } - return quote(value.toString()); + // 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); } /** diff --git a/JSONPointer.java b/JSONPointer.java index 8142f9a..0040e17 100644 --- a/JSONPointer.java +++ b/JSONPointer.java @@ -5,7 +5,9 @@ import static java.lang.String.format; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /* Copyright (c) 2002 JSON.org @@ -181,7 +183,7 @@ public class JSONPointer { * @return the result of the evaluation * @throws JSONPointerException if an error occurs during evaluation */ - public Object queryFrom(Object document) { + public Object queryFrom(Object document) throws JSONPointerException { if (this.refTokens.isEmpty()) { return document; } @@ -205,10 +207,9 @@ public class JSONPointer { * @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 - * JSONPointerException is thrown + * @throws JSONPointerException is thrown if the index is out of bounds */ - @SuppressWarnings("boxing") - private Object readByIndexToken(Object current, String indexToken) { + private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException { try { int index = Integer.parseInt(indexToken); JSONArray currentArr = (JSONArray) current; @@ -216,7 +217,11 @@ public class JSONPointer { throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, currentArr.length())); } - return currentArr.get(index); + 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); } diff --git a/JSONWriter.java b/JSONWriter.java index 549f93e..ac5a805 100644 --- a/JSONWriter.java +++ b/JSONWriter.java @@ -1,6 +1,9 @@ package org.json; import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collection; +import java.util.Map; /* Copyright (c) 2006 JSON.org @@ -117,6 +120,9 @@ public class JSONWriter { } 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') { @@ -164,6 +170,9 @@ public class JSONWriter { 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; @@ -204,7 +213,12 @@ public class JSONWriter { } if (this.mode == 'k') { try { - this.stack[this.top - 1].putOnce(string, Boolean.TRUE); + 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(','); } @@ -214,6 +228,9 @@ public class JSONWriter { 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); } } @@ -280,6 +297,81 @@ public class JSONWriter { 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 @@ -321,6 +413,6 @@ public class JSONWriter { * @throws JSONException If the value is out of sequence. */ public JSONWriter value(Object object) throws JSONException { - return this.append(JSONObject.valueToString(object)); + return this.append(valueToString(object)); } } diff --git a/Property.java b/Property.java index 51b97ed..de3e5dd 100644 --- a/Property.java +++ b/Property.java @@ -25,7 +25,6 @@ SOFTWARE. */ import java.util.Enumeration; -import java.util.Map.Entry; import java.util.Properties; /** @@ -61,10 +60,11 @@ public class Property { public static Properties toProperties(JSONObject jo) throws JSONException { Properties properties = new Properties(); if (jo != null) { - for (final Entry entry : jo.entrySet()) { - Object value = entry.getValue(); + // 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(entry.getKey(), value.toString()); + properties.put(key, value.toString()); } } } diff --git a/XML.java b/XML.java index 4dd9a2c..b2cff20 100644 --- a/XML.java +++ b/XML.java @@ -25,7 +25,6 @@ SOFTWARE. */ import java.util.Iterator; -import java.util.Map.Entry; /** * This provides static methods to convert an XML text into a JSONObject, and to @@ -430,11 +429,49 @@ public class XML { * @return JSON value of this string or the string */ public static Object stringToValue(String string) { - Object ret = JSONObject.stringToValue(string); - if(ret instanceof String){ - return unescape((String)ret); + if (string.equals("")) { + return string; } - return ret; + 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 unescape(string); } /** @@ -482,8 +519,11 @@ public class XML { public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException { JSONObject jo = new JSONObject(); XMLTokener x = new XMLTokener(string); - while (x.more() && x.skipPast("<")) { - parse(x, jo, null, keepStrings); + while (x.more()) { + x.skipPast("<"); + if(x.more()) { + parse(x, jo, null, keepStrings); + } } return jo; } @@ -526,10 +566,10 @@ public class XML { } // Loop thru the keys. + // don't use the new entrySet accessor to maintain Android Support jo = (JSONObject) object; - for (final Entry entry : jo.entrySet()) { - final String key = entry.getKey(); - Object value = entry.getValue(); + for (final String key : jo.keySet()) { + Object value = jo.opt(key); if (value == null) { value = ""; } else if (value.getClass().isArray()) { @@ -540,13 +580,14 @@ public class XML { if ("content".equals(key)) { if (value instanceof JSONArray) { ja = (JSONArray) value; - int i = 0; - for (Object val : ja) { + 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())); - i++; } } else { sb.append(escape(value.toString())); @@ -556,7 +597,10 @@ public class XML { } else if (value instanceof JSONArray) { ja = (JSONArray) value; - for (Object val : ja) { + 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); @@ -597,7 +641,10 @@ public class XML { } else { ja = (JSONArray) object; } - for (Object val : ja) { + 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. diff --git a/XMLTokener.java b/XMLTokener.java index 1c5f2b5..5bed89e 100644 --- a/XMLTokener.java +++ b/XMLTokener.java @@ -297,9 +297,8 @@ public class XMLTokener extends JSONTokener { * 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. - * @throws JSONException */ - public boolean skipPast(String to) throws JSONException { + public void skipPast(String to) { boolean b; char c; int i; @@ -316,7 +315,7 @@ public class XMLTokener extends JSONTokener { for (i = 0; i < length; i += 1) { c = next(); if (c == 0) { - return false; + return; } circle[i] = c; } @@ -343,14 +342,14 @@ public class XMLTokener extends JSONTokener { /* If we exit the loop with b intact, then victory is ours. */ if (b) { - return true; + return; } /* Get the next character. If there isn't one, then defeat is ours. */ c = next(); if (c == 0) { - return false; + return; } /* * Shove the character in the circle buffer and advance the