diff --git a/CookieList.java b/CookieList.java index 8cb4e5ed1..c67ee3aea 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 @@ of this software and associated documentation files (the "Software"), to deal 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 static JSONObject toJSONObject(String string) throws JSONException { */ 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 22635fffd..70b88ee6c 100644 --- a/HTTP.java +++ b/HTTP.java @@ -25,7 +25,6 @@ of this software and associated documentation files (the "Software"), to deal */ import java.util.Locale; -import java.util.Map.Entry; /** * Convert an HTTP header to a JSONObject and back. @@ -145,11 +144,12 @@ public static String toString(JSONObject jo) throws JSONException { 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 c1d50b351..2dcbd2564 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 static String toString(JSONArray ja) throws JSONException { // 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 static String toString(JSONObject jo) throws JSONException { //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 8ad7864df..89c94f4bd 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -1908,6 +1908,8 @@ protected static Number stringToNumber(final String val) throws NumberFormatExce * 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 String toString(int indentFactor) 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 = 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 8142f9a6c..0040e17ba 100644 --- a/JSONPointer.java +++ b/JSONPointer.java @@ -5,7 +5,9 @@ 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 @@ private String unescape(String token) { * @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 Object queryFrom(Object document) { * @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 @@ private Object readByIndexToken(Object current, String indexToken) { 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/JSONTokener.java b/JSONTokener.java index 956efcf33..36bce45c2 100644 --- a/JSONTokener.java +++ b/JSONTokener.java @@ -53,10 +53,12 @@ public class JSONTokener { 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. + * Construct a JSONTokener from a Reader. The caller must close the Reader. * * @param reader A reader. */ @@ -69,12 +71,13 @@ public JSONTokener(Reader reader) { this.previous = 0; this.index = 0; this.character = 1; + this.characterPreviousLine = 0; this.line = 1; } /** - * Construct a JSONTokener from an InputStream. + * Construct a JSONTokener from an InputStream. The caller must close the input stream. * @param inputStream The source. */ public JSONTokener(InputStream inputStream) { @@ -103,12 +106,23 @@ public void back() throws JSONException { if (this.usePrevious || this.index <= 0) { throw new JSONException("Stepping back two steps is not supported"); } - this.index--; - this.character--; + 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). @@ -130,6 +144,8 @@ public static int dehexchar(char c) { } /** + * 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() { @@ -154,7 +170,8 @@ public boolean more() throws JSONException { throw new JSONException("Unable to preserve stream position", e); } try { - if(this.reader.read()<0) { + // -1 is EOF, but next() can not consume the null character '\0' + if(this.reader.read() <= 0) { this.eof = true; return false; } @@ -183,26 +200,39 @@ public char next() throws JSONException { } catch (IOException exception) { throw new JSONException(exception); } - - if (c <= 0) { // End of stream - this.eof = true; - return 0; - } } - this.index += 1; - if (this.previous == '\r') { - this.line += 1; - this.character = c == '\n' ? 0 : 1; - } else if (c == '\n') { - this.line += 1; - this.character = 0; - } else { - this.character += 1; + 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 @@ -447,13 +477,17 @@ public char skipTo(char to) throws JSONException { 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 c; + return 0; } } while (c != to); + this.reader.mark(1); } catch (IOException exception) { throw new JSONException(exception); } @@ -461,7 +495,6 @@ public char skipTo(char to) throws JSONException { return c; } - /** * Make a JSONException to signal a syntax error. * diff --git a/JSONWriter.java b/JSONWriter.java index 549f93ee6..ac5a8056b 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 @@ private JSONWriter append(String string) throws JSONException { } 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 @@ private JSONWriter end(char m, char c) throws JSONException { 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 JSONWriter key(String string) throws JSONException { } 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 JSONWriter key(String string) throws JSONException { 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 @@ private void push(JSONObject jo) throws JSONException { 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 JSONWriter value(long l) throws JSONException { * @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 51b97ed56..de3e5dd64 100644 --- a/Property.java +++ b/Property.java @@ -25,7 +25,6 @@ of this software and associated documentation files (the "Software"), to deal */ import java.util.Enumeration; -import java.util.Map.Entry; import java.util.Properties; /** @@ -61,10 +60,11 @@ public static JSONObject toJSONObject(java.util.Properties properties) throws JS 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 4dd9a2c7c..b2cff20c4 100644 --- a/XML.java +++ b/XML.java @@ -25,7 +25,6 @@ of this software and associated documentation files (the "Software"), to deal */ 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 @@ private static boolean parse(XMLTokener x, JSONObject context, String name, bool * @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 static JSONObject toJSONObject(String string) throws JSONException { 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 static String toString(final Object object, final String tagName) } // 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 static String toString(final Object object, final String tagName) 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 static String toString(final Object object, final String tagName) } 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 static String toString(final Object object, final String tagName) } 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 1c5f2b59d..2ff3affcf 100644 --- a/XMLTokener.java +++ b/XMLTokener.java @@ -297,9 +297,11 @@ public Object nextToken() throws JSONException { * 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 { + // 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; @@ -316,7 +318,7 @@ public boolean skipPast(String to) throws JSONException { for (i = 0; i < length; i += 1) { c = next(); if (c == 0) { - return false; + return; } circle[i] = c; } @@ -343,14 +345,14 @@ public boolean skipPast(String to) throws JSONException { /* 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