From 643b25140f84231667683b8c8179eb1809b710cb Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Fri, 7 Jul 2017 20:48:42 -0400 Subject: [PATCH 01/12] Updates for populateMap based on discussion in #279 and #264 --- JSONObject.java | 55 ++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index 8ad7864..69c6993 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; @@ -1397,39 +1398,41 @@ public class JSONObject { Method[] methods = includeSuperClass ? klass.getMethods() : klass .getDeclaredMethods(); - for (int i = 0; i < methods.length; i += 1) { - try { - Method method = methods[i]; - if (Modifier.isPublic(method.getModifiers())) { - String name = method.getName(); - String key = ""; - if (name.startsWith("get")) { - if ("getClass".equals(name) - || "getDeclaringClass".equals(name)) { - key = ""; - } else { - key = name.substring(3); - } - } else if (name.startsWith("is")) { - key = name.substring(2); + 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 ) { + final String name = method.getName(); + String key; + if (name.startsWith("get")) { + if ("getClass".equals(name) || "getDeclaringClass".equals(name)) { + continue; + } + key = name.substring(3); + } else if (name.startsWith("is")) { + key = name.substring(2); + } else { + continue; + } + if (key.length() > 0 && Character.isUpperCase(key.charAt(0))) { + 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); } - if (key.length() > 0 - && Character.isUpperCase(key.charAt(0)) - && method.getParameterTypes().length == 0) { - 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); - } - Object result = method.invoke(bean, (Object[]) null); + try { + final Object result = method.invoke(bean); if (result != null) { this.map.put(key, wrap(result)); } + } catch (IllegalAccessException ignore) { + } catch (IllegalArgumentException ignore) { + } catch (InvocationTargetException ignore) { } } - } catch (Exception ignore) { } } } From 641b68dd55c0682c3ca32034347fd1972131b123 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Fri, 7 Jul 2017 21:15:11 -0400 Subject: [PATCH 02/12] updates javadoc. --- JSONObject.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index 69c6993..ef2413a 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -277,16 +277,19 @@ public class JSONObject { * "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. - * + *

* 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". - * + *

+ * Methods that return void as well as static + * methods are ignored. + * * @param bean * An object that has getter methods that should be used to make * a JSONObject. @@ -1389,6 +1392,15 @@ public class JSONObject { 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(); From a129ebe8e47df8121fdc3f37801610f682d57ae5 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Sun, 9 Jul 2017 17:36:36 -0400 Subject: [PATCH 03/12] Adds check for resources opened by our bean mapping --- JSONObject.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index ef2413a..171f293 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -1,5 +1,7 @@ package org.json; +import java.io.Closeable; + /* Copyright (c) 2002 JSON.org @@ -1412,8 +1414,10 @@ public class JSONObject { .getDeclaredMethods(); for (final Method method : methods) { final int modifiers = method.getModifiers(); - if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) - && method.getParameterTypes().length == 0 && !method.isBridge() + if (Modifier.isPublic(modifiers) + && !Modifier.isStatic(modifiers) + && method.getParameterTypes().length == 0 + && !method.isBridge() && method.getReturnType() != Void.TYPE ) { final String name = method.getName(); String key; @@ -1427,7 +1431,8 @@ public class JSONObject { } else { continue; } - if (key.length() > 0 && Character.isUpperCase(key.charAt(0))) { + if (key.length() > 0 + && Character.isUpperCase(key.charAt(0))) { if (key.length() == 1) { key = key.toLowerCase(Locale.ROOT); } else if (!Character.isUpperCase(key.charAt(1))) { @@ -1439,6 +1444,14 @@ public class JSONObject { 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) { From 5c80c9157d86b5f22bf5b905b23a4039d37de348 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Sun, 9 Jul 2017 18:47:09 -0400 Subject: [PATCH 04/12] fixes malformed javadoc --- JSONArray.java | 2 +- JSONObject.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index a402d67..197ad97 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -1230,7 +1230,7 @@ public class JSONArray implements Iterable { * Queries and returns a value from this object using {@code jsonPointer}, or * returns null if the query fails due to a missing key. * - * @param The JSON pointer + * @param jsonPointer The JSON pointer * @return the queried value or {@code null} * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax */ diff --git a/JSONObject.java b/JSONObject.java index 171f293..800e589 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -1704,7 +1704,7 @@ public class JSONObject { * Queries and returns a value from this object using {@code jsonPointer}, or * returns null if the query fails due to a missing key. * - * @param The JSON pointer + * @param jsonPointer The JSON pointer * @return the queried value or {@code null} * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax */ From 4dbc5ef8036017c05c107d5e62be322213726930 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Sun, 9 Jul 2017 18:48:40 -0400 Subject: [PATCH 05/12] fixes malformed javadoc --- XML.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XML.java b/XML.java index 4dd9a2c..36f44c8 100644 --- a/XML.java +++ b/XML.java @@ -423,7 +423,7 @@ public class XML { } /** - * This method is the same as {@link JSONObject.stringToValue(String)} + * This method is the same as {@link JSONObject#stringToValue(String)} * except that this also tries to unescape String values. * * @param string String to convert From 6f238a369812246c33e841684904f423709fff8d Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Sat, 15 Jul 2017 12:17:27 -0400 Subject: [PATCH 06/12] Update javadoc according to issue #356. --- JSONArray.java | 48 +++++++++++++++++++++++++++++++++++++++--------- JSONObject.java | 40 +++++++++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/JSONArray.java b/JSONArray.java index a402d67..8775a19 100644 --- a/JSONArray.java +++ b/JSONArray.java @@ -1230,7 +1230,7 @@ public class JSONArray implements Iterable { * Queries and returns a value from this object using {@code jsonPointer}, or * returns null if the query fails due to a missing key. * - * @param The JSON pointer + * @param jsonPointer The JSON pointer * @return the queried value or {@code null} * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax */ @@ -1323,8 +1323,9 @@ public class JSONArray implements Iterable { * 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. @@ -1339,9 +1340,24 @@ public class JSONArray implements Iterable { } /** - * Make a pretty-printed JSON text of this JSONArray. Warning: This method - * assumes that the data structure is acyclical. - * + * 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 @@ -1360,8 +1376,9 @@ public class JSONArray implements Iterable { /** * 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 @@ -1371,10 +1388,23 @@ public class JSONArray implements Iterable { } /** - * Write the contents of the JSONArray as JSON text to a writer. For - * compactness, no whitespace is added. - *

+ * 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 diff --git a/JSONObject.java b/JSONObject.java index 8ad7864..46ed869 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -1676,7 +1676,7 @@ public class JSONObject { * Queries and returns a value from this object using {@code jsonPointer}, or * returns null if the query fails due to a missing key. * - * @param The JSON pointer + * @param jsonPointer The JSON pointer * @return the queried value or {@code null} * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax */ @@ -2002,9 +2002,10 @@ public class JSONObject { * 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 @@ -2021,8 +2022,20 @@ public class JSONObject { /** * 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. @@ -2172,9 +2185,10 @@ public class JSONObject { /** * 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 */ @@ -2238,8 +2252,20 @@ public class JSONObject { /** * 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 From 7fed0230806031033eaeda8ed1a2851103b91fee Mon Sep 17 00:00:00 2001 From: Miguel Date: Wed, 9 Aug 2017 21:52:36 -0400 Subject: [PATCH 07/12] Update to include error location when creating JSONObject from string/text --- JSONObject.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/JSONObject.java b/JSONObject.java index fcc0c91..9d5fc87 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -231,7 +231,21 @@ public class JSONObject { if (c != ':') { throw x.syntaxError("Expected a ':' after a key"); } - this.putOnce(key, x.nextValue()); + + // Replace: this.putOnce(key, x.nextValue()); + // Use syntaxError(..) to include error location + + if (key != null) { + // Check if key exists + if (this.opt(key) != null) { + 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 ','. From 7d8353401ad0943a00423cb4f3e7d71a4b6a162f Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 10 Aug 2017 19:05:57 -0400 Subject: [PATCH 08/12] Adding JSONTokener.back() just before throwing JSONException This forces JSONTokener.syntaxError(..) to point to the last character of the duplicate key. --- JSONObject.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/JSONObject.java b/JSONObject.java index 9d5fc87..28f401e 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -232,12 +232,13 @@ public class JSONObject { throw x.syntaxError("Expected a ':' after a key"); } - // Replace: this.putOnce(key, x.nextValue()); // Use syntaxError(..) to include error location if (key != null) { // Check if key exists if (this.opt(key) != null) { + // back one token to point to the last key character + x.back(); throw x.syntaxError("Duplicate key \"" + key + "\""); } // Only add value if non-null From f177c972589463674c4e3883a494cf4c1f186721 Mon Sep 17 00:00:00 2001 From: Miguel Date: Thu, 10 Aug 2017 19:12:41 -0400 Subject: [PATCH 09/12] Replacing tabs with 4-spaces --- JSONObject.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index 28f401e..a55ecf8 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -236,15 +236,15 @@ public class JSONObject { if (key != null) { // Check if key exists - if (this.opt(key) != null) { - // back one token to point to the last key character - x.back(); + if (this.opt(key) != null) { + // back one token to point to the last key character + x.back(); throw x.syntaxError("Duplicate key \"" + key + "\""); - } + } // Only add value if non-null Object value = x.nextValue(); if (value!=null) { - this.put(key, value); + this.put(key, value); } } From 2e0a8137bd911736ded8bda32c07203a0a97e9a6 Mon Sep 17 00:00:00 2001 From: Miguel Date: Mon, 14 Aug 2017 13:01:31 -0400 Subject: [PATCH 10/12] Removed JSONTokener.back() --- JSONObject.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/JSONObject.java b/JSONObject.java index a55ecf8..1b7b0a1 100644 --- a/JSONObject.java +++ b/JSONObject.java @@ -237,8 +237,7 @@ public class JSONObject { if (key != null) { // Check if key exists if (this.opt(key) != null) { - // back one token to point to the last key character - x.back(); + // key already exists throw x.syntaxError("Duplicate key \"" + key + "\""); } // Only add value if non-null From de855c50aa4435fa54510e299e10c1481ef59ea0 Mon Sep 17 00:00:00 2001 From: "John J. Aylward" Date: Sat, 19 Aug 2017 18:21:56 -0400 Subject: [PATCH 11/12] Fixes #361. * Removes unescape from the XML class calls * fixes bug with unescape method * moves unescape logic into the XMLTokener class for more consistency --- JSONML.java | 2 +- XML.java | 38 +++++--------------------------------- XMLTokener.java | 33 +++++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/JSONML.java b/JSONML.java index c1d50b3..be16693 100644 --- a/JSONML.java +++ b/JSONML.java @@ -174,7 +174,7 @@ public class JSONML { if (!(token instanceof String)) { throw x.syntaxError("Missing value"); } - newjo.accumulate(attribute, keepStrings ? XML.unescape((String)token) :XML.stringToValue((String)token)); + newjo.accumulate(attribute, keepStrings ? ((String)token) :XML.stringToValue((String)token)); token = null; } else { newjo.accumulate(attribute, ""); diff --git a/XML.java b/XML.java index 36f44c8..faa5b65 100644 --- a/XML.java +++ b/XML.java @@ -141,7 +141,7 @@ public class XML { if (mustEscape(cp)) { sb.append("&#x"); sb.append(Integer.toHexString(cp)); - sb.append(";"); + sb.append(';'); } else { sb.appendCodePoint(cp); } @@ -191,31 +191,7 @@ public class XML { final int semic = string.indexOf(';', i); if (semic > i) { final String entity = string.substring(i + 1, semic); - if (entity.charAt(0) == '#') { - int cp; - if (entity.charAt(1) == 'x') { - // hex encoded unicode - cp = Integer.parseInt(entity.substring(2), 16); - } else { - // decimal encoded unicode - cp = Integer.parseInt(entity.substring(1)); - } - sb.appendCodePoint(cp); - } else { - if ("quot".equalsIgnoreCase(entity)) { - sb.append('"'); - } else if ("amp".equalsIgnoreCase(entity)) { - sb.append('&'); - } else if ("apos".equalsIgnoreCase(entity)) { - sb.append('\''); - } else if ("lt".equalsIgnoreCase(entity)) { - sb.append('<'); - } else if ("gt".equalsIgnoreCase(entity)) { - sb.append('>'); - } else {// unsupported xml entity. leave encoded - sb.append('&').append(entity).append(';'); - } - } + sb.append(XMLTokener.unescapeEntity(entity)); // skip past the entity we just parsed. i += entity.length() + 1; } else { @@ -364,7 +340,7 @@ public class XML { throw x.syntaxError("Missing value"); } jsonobject.accumulate(string, - keepStrings ? unescape((String)token) : stringToValue((String) token)); + keepStrings ? ((String)token) : stringToValue((String) token)); token = null; } else { jsonobject.accumulate(string, ""); @@ -396,7 +372,7 @@ public class XML { string = (String) token; if (string.length() > 0) { jsonobject.accumulate("content", - keepStrings ? unescape(string) : stringToValue(string)); + keepStrings ? string : stringToValue(string)); } } else if (token == LT) { @@ -430,11 +406,7 @@ 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); - } - return ret; + return JSONObject.stringToValue(string); } /** diff --git a/XMLTokener.java b/XMLTokener.java index 1c5f2b5..fb54da3 100644 --- a/XMLTokener.java +++ b/XMLTokener.java @@ -138,8 +138,37 @@ public class XMLTokener extends JSONTokener { } } String string = sb.toString(); - Object object = entity.get(string); - return object != null ? object : ampersand + string + ";"; + 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(); } From cdf3cf7f814ad6a75ac51b1ec74f0d59ce8f6e6a Mon Sep 17 00:00:00 2001 From: Sean Leary Date: Tue, 17 Oct 2017 20:05:29 -0500 Subject: [PATCH 12/12] Update README --- README | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README b/README index 2fbae18..9783046 100644 --- a/README +++ b/README @@ -89,6 +89,8 @@ invalid number formats (1.2e6.3) will cause errors as such documents can not be reliably. Release history: +20171018 Checkpoint for recent commits. + 20170516 Roll up recent commits. 20160810 Revert code that was breaking opt*() methods.