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 instanceof Number && valueOther instanceof Number) {
+ if (!isNumberSimilar((Number)valueThis, (Number)valueOther)) {
+ return false;
+ }
+ } else if (valueThis instanceof JSONString && valueOther instanceof JSONString) {
+ if (!((JSONString) valueThis).toJSONString().equals(((JSONString) valueOther).toJSONString())) {
+ return false;
+ }
+ } else if (!valueThis.equals(valueOther)) {
+ return false;
+ }
+ }
+ return true;
+ } catch (Throwable exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Compares two numbers to see if they are similar.
+ *
+ * If either of the numbers are Double or Float instances, then they are checked to have
+ * a finite value. If either value is not finite (NaN or ±infinity), then this
+ * function will always return false. If both numbers are finite, they are first checked
+ * to be the same type and implement {@link Comparable}. If they do, then the actual
+ * {@link Comparable#compareTo(Object)} is called. If they are not the same type, or don't
+ * implement Comparable, then they are converted to {@link BigDecimal}s. Finally the
+ * BigDecimal values are compared using {@link BigDecimal#compareTo(BigDecimal)}.
+ *
+ * @param l the Left value to compare. Can not be null.
+ * @param r the right value to compare. Can not be null.
+ * @return true if the numbers are similar, false otherwise.
+ */
+ static boolean isNumberSimilar(Number l, Number r) {
+ if (!numberIsFinite(l) || !numberIsFinite(r)) {
+ // non-finite numbers are never similar
+ return false;
+ }
+
+ // if the classes are the same and implement Comparable
+ // then use the built in compare first.
+ if(l.getClass().equals(r.getClass()) && l instanceof Comparable) {
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ int compareTo = ((Comparable)l).compareTo(r);
+ return compareTo==0;
+ }
+
+ // BigDecimal should be able to handle all of our number types that we support through
+ // documentation. Convert to BigDecimal first, then use the Compare method to
+ // decide equality.
+ final BigDecimal lBigDecimal = objectToBigDecimal(l, null, false);
+ final BigDecimal rBigDecimal = objectToBigDecimal(r, null, false);
+ if (lBigDecimal == null || rBigDecimal == null) {
+ return false;
+ }
+ return lBigDecimal.compareTo(rBigDecimal) == 0;
+ }
+
+ private static boolean numberIsFinite(Number n) {
+ if (n instanceof Double && (((Double) n).isInfinite() || ((Double) n).isNaN())) {
+ return false;
+ } else if (n instanceof Float && (((Float) n).isInfinite() || ((Float) n).isNaN())) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 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)) {
+ // Use a BigDecimal all the time so we keep the original
+ // representation. BigDecimal doesn't support -0.0, ensure we
+ // keep that by forcing a decimal.
+ try {
+ BigDecimal bd = new BigDecimal(val);
+ if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+ return Double.valueOf(-0.0);
+ }
+ return bd;
+ } catch (NumberFormatException retryAsDouble) {
+ // this is to support "Hex Floats" like this: 0x1.0P-1074
+ try {
+ Double d = Double.valueOf(val);
+ if(d.isNaN() || d.isInfinite()) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ return d;
+ } catch (NumberFormatException ignore) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ if(initial == '0' && val.length() > 1) {
+ char at1 = val.charAt(1);
+ if(at1 >= '0' && at1 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ } else if (initial == '-' && val.length() > 2) {
+ char at1 = val.charAt(1);
+ char at2 = val.charAt(2);
+ if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // BigInteger down conversion: We use a similar bitLength 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.
+ 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. can not be null.
+ * @return A simple JSON value.
+ * @throws NullPointerException
+ * Thrown if the string is null.
+ */
+ // 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 ("".equals(string)) {
+ return string;
+ }
+
+ // check JSON key words true/false/null
+ if ("true".equalsIgnoreCase(string)) {
+ return Boolean.TRUE;
+ }
+ if ("false".equalsIgnoreCase(string)) {
+ return Boolean.FALSE;
+ }
+ if ("null".equalsIgnoreCase(string)) {
+ 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 {
+ return stringToNumber(string);
+ } 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 instanceof Number && !numberIsFinite((Number) o)) {
+ 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
{@code 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:
{@code {
+ * "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.
+ */
+ @SuppressWarnings("resource")
+ 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) {
+ return wrap(object, null);
+ }
+
+ private static Object wrap(Object object, Set objectsRecord) {
+ try {
+ if (NULL.equals(object)) {
+ 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();
+ }
+ if (objectsRecord != null) {
+ return new JSONObject(object, objectsRecord);
+ }
+ return new JSONObject(object);
+ }
+ catch (JSONException exception) {
+ throw exception;
+ } 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.
+ *
+ * @param writer the writer object
+ * @return The writer.
+ * @throws JSONException if a called function has an error
+ */
+ public Writer write(Writer writer) throws JSONException {
+ return this.write(writer, 0, 0);
+ }
+
+ @SuppressWarnings("resource")
+ 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);
+ if(NUMBER_PATTERN.matcher(numberAsString).matches()) {
+ writer.write(numberAsString);
+ } else {
+ // 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
{@code 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:
{@code {
+ * "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 if a called function has an error or a write error
+ * occurs
+ */
+ @SuppressWarnings("resource")
+ public Writer write(Writer writer, int indentFactor, int indent)
+ throws JSONException {
+ try {
+ boolean needsComma = 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 (needsComma) {
+ 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);
+ }
+ needsComma = 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;
+ }
+
+ /**
+ * Create a new JSONException in a common format for incorrect conversions.
+ * @param key name of the key
+ * @param valueType the type of value being coerced to
+ * @param cause optional cause of the coercion failure
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException wrongValueFormatException(
+ String key,
+ String valueType,
+ Object value,
+ Throwable cause) {
+ if(value == null) {
+
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (null)."
+ , cause);
+ }
+ // don't try to toString collections or known object types that could be large.
+ if(value instanceof Map || value instanceof Iterable || value instanceof JSONObject) {
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + ")."
+ , cause);
+ }
+ return new JSONException(
+ "JSONObject[" + quote(key) + "] is not a " + valueType + " (" + value.getClass() + " : " + value + ")."
+ , cause);
+ }
+
+ /**
+ * Create a new JSONException in a common format for recursive object definition.
+ * @param key name of the key
+ * @return JSONException that can be thrown.
+ */
+ private static JSONException recursivelyDefinedObjectException(String key) {
+ return new JSONException(
+ "JavaBean object contains recursively defined member variable of key " + quote(key)
+ );
+ }
+}
diff --git a/common/src/main/java/org/json/JSONPointer.java b/common/src/main/java/org/json/JSONPointer.java
new file mode 100644
index 0000000..963fdec
--- /dev/null
+++ b/common/src/main/java/org/json/JSONPointer.java
@@ -0,0 +1,275 @@
+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;
+
+/*
+Public Domain.
+*/
+
+/**
+ * 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.
+ * @return a JSONPointer object
+ */
+ 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);
+ }
+
+ /**
+ * @see rfc6901 section 3
+ */
+ private static String unescape(String token) {
+ return token.replace("~1", "/").replace("~0", "~");
+ }
+
+ /**
+ * 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 static 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 %s is out of bounds - the array has %d elements", indexToken,
+ Integer.valueOf(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.
+ * @param token the JSONPointer segment value to be escaped
+ * @return the escaped value for the token
+ *
+ * @see rfc6901 section 3
+ */
+ private static String escape(String token) {
+ return token.replace("~", "~0")
+ .replace("/", "~1");
+ }
+
+ /**
+ * Returns a string representing the JSONPointer path value using URI
+ * fragment identifier representation
+ * @return a uri fragment string
+ */
+ 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/common/src/main/java/org/json/JSONPointerException.java b/common/src/main/java/org/json/JSONPointerException.java
new file mode 100644
index 0000000..a0e128c
--- /dev/null
+++ b/common/src/main/java/org/json/JSONPointerException.java
@@ -0,0 +1,25 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+/**
+ * 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/common/src/main/java/org/json/JSONPropertyIgnore.java b/common/src/main/java/org/json/JSONPropertyIgnore.java
new file mode 100644
index 0000000..d3a5bc5
--- /dev/null
+++ b/common/src/main/java/org/json/JSONPropertyIgnore.java
@@ -0,0 +1,23 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+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;
+
+/**
+ * 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.
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+public @interface JSONPropertyIgnore { }
diff --git a/common/src/main/java/org/json/JSONPropertyName.java b/common/src/main/java/org/json/JSONPropertyName.java
new file mode 100644
index 0000000..4391bb7
--- /dev/null
+++ b/common/src/main/java/org/json/JSONPropertyName.java
@@ -0,0 +1,27 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+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;
+
+/**
+ * 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.
+ */
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+public @interface JSONPropertyName {
+ /**
+ * @return The name of the property as to be used in the JSON Object.
+ */
+ String value();
+}
diff --git a/common/src/main/java/org/json/JSONString.java b/common/src/main/java/org/json/JSONString.java
new file mode 100644
index 0000000..cd8d184
--- /dev/null
+++ b/common/src/main/java/org/json/JSONString.java
@@ -0,0 +1,23 @@
+package org.json;
+
+/*
+Public Domain.
+ */
+
+/**
+ * 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/common/src/main/java/org/json/JSONStringer.java b/common/src/main/java/org/json/JSONStringer.java
new file mode 100644
index 0000000..2f6cf9e
--- /dev/null
+++ b/common/src/main/java/org/json/JSONStringer.java
@@ -0,0 +1,59 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+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 200 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/common/src/main/java/org/json/JSONTokener.java b/common/src/main/java/org/json/JSONTokener.java
new file mode 100644
index 0000000..8c98c77
--- /dev/null
+++ b/common/src/main/java/org/json/JSONTokener.java
@@ -0,0 +1,525 @@
+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;
+
+/*
+Public Domain.
+ */
+
+/**
+ * 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;
+ }
+
+ /**
+ * Get the last character read from the input or '\0' if nothing has been read yet.
+ * @return the last character read from the input.
+ */
+ protected char getPrevious() { 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();
+ try {
+ return new JSONObject(this);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ case '[':
+ this.back();
+ try {
+ return new JSONArray(this);
+ } catch (StackOverflowError e) {
+ throw new JSONException("JSON Array or Object depth too large to process.", e);
+ }
+ }
+
+ /*
+ * 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();
+ }
+ if (!this.eof) {
+ 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/common/src/main/java/org/json/JSONWriter.java b/common/src/main/java/org/json/JSONWriter.java
new file mode 100644
index 0000000..11f4a5c
--- /dev/null
+++ b/common/src/main/java/org/json/JSONWriter.java
@@ -0,0 +1,394 @@
+package org.json;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/*
+Public Domain.
+*/
+
+/**
+ * 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.
+ * @param w an appendable object
+ */
+ 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) {
+ String object;
+ try {
+ object = ((JSONString) value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ if (object != null) {
+ return 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);
+ if(JSONObject.NUMBER_PATTERN.matcher(numberAsString).matches()) {
+ // Close enough to a JSON number that we will return it unquoted
+ return numberAsString;
+ }
+ // 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 if a called function has an error
+ */
+ 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 if a called function has an error
+ */
+ 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/common/src/main/java/org/json/Property.java b/common/src/main/java/org/json/Property.java
new file mode 100644
index 0000000..83694c0
--- /dev/null
+++ b/common/src/main/java/org/json/Property.java
@@ -0,0 +1,55 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+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 if a called function has an error
+ */
+ 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 if a called function has an error
+ */
+ 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/common/src/main/java/org/json/XML.java b/common/src/main/java/org/json/XML.java
new file mode 100644
index 0000000..925f056
--- /dev/null
+++ b/common/src/main/java/org/json/XML.java
@@ -0,0 +1,976 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+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
{@code '>'. } */
+ 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 = '/';
+
+ /**
+ * Null attribute name
+ */
+ public static final String NULL_ATTR = "xsi:nil";
+
+ public static final String TYPE_ATTR = "xsi:type";
+
+ /**
+ * 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:
+ *
+ * {@code
+ * & (ampersand) is replaced by &
+ * < (less than) is replaced by <
+ * > (greater than) is replaced by >
+ * " (double quote) is replaced by "
+ * ' (single quote / apostrophe) is replaced by '
+ * }
+ *
+ * @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("");
+ 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.
+ * @param config
+ * The XML parser configuration.
+ * @param currentNestingDepth
+ * The current nesting depth.
+ * @return true if the close tag is processed.
+ * @throws JSONException Thrown if any parsing error occurs.
+ */
+ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config, int currentNestingDepth)
+ throws JSONException {
+ char c;
+ int i;
+ JSONObject jsonObject = null;
+ String string;
+ String tagName;
+ Object token;
+ XMLXsiTypeConverter> xmlXsiTypeConverter;
+
+ // 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(config.getcDataTagName(), 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) {
+
+ //
+ x.skipPast("?>");
+ return false;
+ } else if (token == SLASH) {
+
+ // Close tag
+
+ token = x.nextToken();
+ if (name == null) {
+ throw x.syntaxError("Mismatched close tag " + token);
+ }
+ if (!token.equals(name)) {
+ throw x.syntaxError("Mismatched " + name + " and " + token);
+ }
+ if (x.nextToken() != GT) {
+ throw x.syntaxError("Misshaped close tag");
+ }
+ return true;
+
+ } else if (token instanceof Character) {
+ throw x.syntaxError("Misshaped tag");
+
+ // Open tag <
+
+ } else {
+ tagName = (String) token;
+ token = null;
+ jsonObject = new JSONObject();
+ boolean nilAttributeFound = false;
+ xmlXsiTypeConverter = null;
+ for (;;) {
+ if (token == null) {
+ token = x.nextToken();
+ }
+ // attribute = value
+ if (token instanceof String) {
+ string = (String) token;
+ token = x.nextToken();
+ if (token == EQ) {
+ token = x.nextToken();
+ if (!(token instanceof String)) {
+ throw x.syntaxError("Missing value");
+ }
+
+ if (config.isConvertNilAttributeToNull()
+ && NULL_ATTR.equals(string)
+ && Boolean.parseBoolean((String) token)) {
+ nilAttributeFound = true;
+ } else if(config.getXsiTypeMap() != null && !config.getXsiTypeMap().isEmpty()
+ && TYPE_ATTR.equals(string)) {
+ xmlXsiTypeConverter = config.getXsiTypeMap().get(token);
+ } else if (!nilAttributeFound) {
+ jsonObject.accumulate(string,
+ config.isKeepStrings()
+ ? ((String) token)
+ : stringToValue((String) token));
+ }
+ token = null;
+ } else {
+ jsonObject.accumulate(string, "");
+ }
+
+
+ } else if (token == SLASH) {
+ // Empty tag <.../>
+ if (x.nextToken() != GT) {
+ throw x.syntaxError("Misshaped tag");
+ }
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (nilAttributeFound) {
+ context.append(tagName, JSONObject.NULL);
+ } else if (jsonObject.length() > 0) {
+ context.append(tagName, jsonObject);
+ } else {
+ context.put(tagName, new JSONArray());
+ }
+ } else {
+ if (nilAttributeFound) {
+ context.accumulate(tagName, JSONObject.NULL);
+ } else 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) {
+ if(xmlXsiTypeConverter != null) {
+ jsonObject.accumulate(config.getcDataTagName(),
+ stringToValue(string, xmlXsiTypeConverter));
+ } else {
+ jsonObject.accumulate(config.getcDataTagName(),
+ config.isKeepStrings() ? string : stringToValue(string));
+ }
+ }
+
+ } else if (token == LT) {
+ // Nested element
+ if (currentNestingDepth == config.getMaxNestingDepth()) {
+ throw x.syntaxError("Maximum nesting depth of " + config.getMaxNestingDepth() + " reached");
+ }
+
+ if (parse(x, jsonObject, tagName, config, currentNestingDepth + 1)) {
+ if (config.getForceList().contains(tagName)) {
+ // Force the value to be an array
+ if (jsonObject.length() == 0) {
+ context.put(tagName, new JSONArray());
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.append(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ context.append(tagName, jsonObject);
+ }
+ } else {
+ if (jsonObject.length() == 0) {
+ context.accumulate(tagName, "");
+ } else if (jsonObject.length() == 1
+ && jsonObject.opt(config.getcDataTagName()) != null) {
+ context.accumulate(tagName, jsonObject.opt(config.getcDataTagName()));
+ } else {
+ context.accumulate(tagName, jsonObject);
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+ } else {
+ throw x.syntaxError("Misshaped tag");
+ }
+ }
+ }
+ }
+
+ /**
+ * This method tries to convert the given string value to the target object
+ * @param string String to convert
+ * @param typeConverter value converter to convert string to integer, boolean e.t.c
+ * @return JSON value of this string or the string
+ */
+ public static Object stringToValue(String string, XMLXsiTypeConverter> typeConverter) {
+ if(typeConverter != null) {
+ return typeConverter.convert(string);
+ }
+ return stringToValue(string);
+ }
+
+ /**
+ * 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.
+ // This method should not make calls out of the XML object.
+ public static Object stringToValue(String string) {
+ if ("".equals(string)) {
+ return string;
+ }
+
+ // check JSON key words true/false/null
+ if ("true".equalsIgnoreCase(string)) {
+ return Boolean.TRUE;
+ }
+ if ("false".equalsIgnoreCase(string)) {
+ return Boolean.FALSE;
+ }
+ if ("null".equalsIgnoreCase(string)) {
+ 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 {
+ return stringToNumber(string);
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
+
+ /**
+ * direct copy of {@link JSONObject#stringToNumber(String)} to maintain Android support.
+ */
+ private static Number stringToNumber(final String val) throws NumberFormatException {
+ char initial = val.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ // Use a BigDecimal all the time so we keep the original
+ // representation. BigDecimal doesn't support -0.0, ensure we
+ // keep that by forcing a decimal.
+ try {
+ BigDecimal bd = new BigDecimal(val);
+ if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) {
+ return Double.valueOf(-0.0);
+ }
+ return bd;
+ } catch (NumberFormatException retryAsDouble) {
+ // this is to support "Hex Floats" like this: 0x1.0P-1074
+ try {
+ Double d = Double.valueOf(val);
+ if(d.isNaN() || d.isInfinite()) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ return d;
+ } catch (NumberFormatException ignore) {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ }
+ // block items like 00 01 etc. Java number parsers treat these as Octal.
+ if(initial == '0' && val.length() > 1) {
+ char at1 = val.charAt(1);
+ if(at1 >= '0' && at1 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ } else if (initial == '-' && val.length() > 2) {
+ char at1 = val.charAt(1);
+ char at2 = val.charAt(2);
+ if(at1 == '0' && at2 >= '0' && at2 <= '9') {
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // BigInteger down conversion: We use a similar bitLength 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.
+ 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.");
+ }
+
+ /**
+ * direct copy of {@link JSONObject#isDecimalNotation(String)} to maintain Android support.
+ */
+ private static boolean isDecimalNotation(final String val) {
+ return val.indexOf('.') > -1 || val.indexOf('e') > -1
+ || val.indexOf('E') > -1 || "-0".equals(val);
+ }
+
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
+ * 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, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * 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 {@code
+ * <[ [ ]]>}
+ * 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, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * 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 {@code
+ * <[ [ ]]>}
+ * 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 {
+ if(keepStrings) {
+ return toJSONObject(reader, XMLParserConfiguration.KEEP_STRINGS);
+ }
+ return toJSONObject(reader, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * 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 {@code
+ * <[ [ ]]>}
+ * 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 config Configuration options for the parser
+ * @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, XMLParserConfiguration config) throws JSONException {
+ JSONObject jo = new JSONObject();
+ XMLTokener x = new XMLTokener(reader);
+ while (x.more()) {
+ x.skipPast("<");
+ if(x.more()) {
+ parse(x, jo, null, config, 0);
+ }
+ }
+ 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 {@code
+ * <[ [ ]]>}
+ * 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 well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and {@code
+ * <[ [ ]]>}
+ * 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 config Configuration options for the parser.
+ * @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, XMLParserConfiguration config) throws JSONException {
+ return toJSONObject(new StringReader(string), config);
+ }
+
+ /**
+ * 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, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * 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) {
+ return toString(object, tagName, XMLParserConfiguration.ORIGINAL);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, final XMLParserConfiguration config)
+ throws JSONException {
+ return toString(object, tagName, config, 0, 0);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, element-normal XML string,
+ * either pretty print or single-lined depending on indent factor.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The current ident level in spaces.
+ * @return
+ * @throws JSONException
+ */
+ private static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor, int indent)
+ throws JSONException {
+ StringBuilder sb = new StringBuilder();
+ JSONArray ja;
+ JSONObject jo;
+ String string;
+
+ if (object instanceof JSONObject) {
+
+ // Emit
+ if (tagName != null) {
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(tagName);
+ sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ indent += indentFactor;
+ }
+ }
+
+ // 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 (key.equals(config.getcDataTagName())) {
+ 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, null, config, indentFactor, indent));
+ sb.append("");
+ sb.append(key);
+ sb.append('>');
+ } else {
+ sb.append(toString(val, key, config, indentFactor, indent));
+ }
+ }
+ } else if ("".equals(value)) {
+ sb.append(indent(indent));
+ sb.append('<');
+ sb.append(key);
+ sb.append("/>");
+ if(indentFactor > 0){
+ sb.append("\n");
+ }
+
+ // Emit a new tag
+
+ } else {
+ sb.append(toString(value, key, config, indentFactor, indent));
+ }
+ }
+ if (tagName != null) {
+
+ // Emit the close tag
+ sb.append(indent(indent - indentFactor));
+ sb.append("");
+ sb.append(tagName);
+ sb.append('>');
+ if(indentFactor > 0){
+ sb.append("\n");
+ }
+ }
+ 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, config, indentFactor, indent));
+ }
+ return sb.toString();
+ }
+
+
+ string = (object == null) ? "null" : escape(object.toString());
+
+ if(tagName == null){
+ return indent(indent) + "\"" + string + "\"" + ((indentFactor > 0) ? "\n" : "");
+ } else if(string.length() == 0){
+ return indent(indent) + "<" + tagName + "/>" + ((indentFactor > 0) ? "\n" : "");
+ } else {
+ return indent(indent) + "<" + tagName
+ + ">" + string + "" + tagName + ">" + ((indentFactor > 0) ? "\n" : "");
+ }
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(Object object, int indentFactor){
+ return toString(object, null, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, int indentFactor) {
+ return toString(object, tagName, XMLParserConfiguration.ORIGINAL, indentFactor);
+ }
+
+ /**
+ * Convert a JSONObject into a well-formed, pretty printed element-normal XML string.
+ *
+ * @param object
+ * A JSONObject.
+ * @param tagName
+ * The optional name of the enclosing tag.
+ * @param config
+ * Configuration that can control output to XML.
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error parsing the string
+ */
+ public static String toString(final Object object, final String tagName, final XMLParserConfiguration config, int indentFactor)
+ throws JSONException {
+ return toString(object, tagName, config, indentFactor, 0);
+ }
+
+ /**
+ * Return a String consisting of a number of space characters specified by indent
+ *
+ * @param indent
+ * The number of spaces to be appended to the String.
+ * @return
+ */
+ private static final String indent(int indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+}
diff --git a/common/src/main/java/org/json/XMLParserConfiguration.java b/common/src/main/java/org/json/XMLParserConfiguration.java
new file mode 100644
index 0000000..103023e
--- /dev/null
+++ b/common/src/main/java/org/json/XMLParserConfiguration.java
@@ -0,0 +1,350 @@
+package org.json;
+/*
+Public Domain.
+*/
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Configuration object for the XML parser. The configuration is immutable.
+ * @author AylwardJ
+ */
+@SuppressWarnings({""})
+public class XMLParserConfiguration {
+ /**
+ * Used to indicate there's no defined limit to the maximum nesting depth when parsing a XML
+ * document to JSON.
+ */
+ public static final int UNDEFINED_MAXIMUM_NESTING_DEPTH = -1;
+
+ /**
+ * The default maximum nesting depth when parsing a XML document to JSON.
+ */
+ public static final int DEFAULT_MAXIMUM_NESTING_DEPTH = 512;
+
+ /** Original Configuration of the XML Parser. */
+ public static final XMLParserConfiguration ORIGINAL
+ = new XMLParserConfiguration();
+ /** Original configuration of the XML Parser except that values are kept as strings. */
+ public static final XMLParserConfiguration KEEP_STRINGS
+ = new XMLParserConfiguration().withKeepStrings(true);
+
+ /**
+ * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ */
+ private boolean keepStrings;
+
+ /**
+ * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+ * been the value "content" but can be changed. Use null to indicate no CDATA
+ * processing.
+ */
+ private String cDataTagName;
+
+ /**
+ * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+ * should be kept as attribute(false), or they should be converted to
+ * null(true)
+ */
+ private boolean convertNilAttributeToNull;
+
+ /**
+ * This will allow type conversion for values in XML if xsi:type attribute is defined
+ */
+ private Map> xsiTypeMap;
+
+ /**
+ * When parsing the XML into JSON, specifies the tags whose values should be converted
+ * to arrays
+ */
+ private Set forceList;
+
+ /**
+ * The maximum nesting depth when parsing a XML document to JSON.
+ */
+ private int maxNestingDepth = DEFAULT_MAXIMUM_NESTING_DEPTH;
+
+ /**
+ * Default parser configuration. Does not keep strings (tries to implicitly convert
+ * values), and the CDATA Tag Name is "content".
+ */
+ public XMLParserConfiguration () {
+ this.keepStrings = false;
+ this.cDataTagName = "content";
+ this.convertNilAttributeToNull = false;
+ this.xsiTypeMap = Collections.emptyMap();
+ this.forceList = Collections.emptySet();
+ }
+
+ /**
+ * Configure the parser string processing and use the default CDATA Tag Name as "content".
+ * @param keepStrings true to parse all values as string.
+ * false to try and convert XML string values into a JSON value.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed in a future release.
+ */
+ @Deprecated
+ public XMLParserConfiguration (final boolean keepStrings) {
+ this(keepStrings, "content", false);
+ }
+
+ /**
+ * Configure the parser string processing to try and convert XML values to JSON values and
+ * use the passed CDATA Tag Name the processing value. Pass null to
+ * disable CDATA processing
+ * @param cDataTagName null to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed in a future release.
+ */
+ @Deprecated
+ public XMLParserConfiguration (final String cDataTagName) {
+ this(false, cDataTagName, false);
+ }
+
+ /**
+ * Configure the parser to use custom settings.
+ * @param keepStrings true to parse all values as string.
+ * false to try and convert XML string values into a JSON value.
+ * @param cDataTagName null to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed in a future release.
+ */
+ @Deprecated
+ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = false;
+ }
+
+ /**
+ * Configure the parser to use custom settings.
+ * @param keepStrings true to parse all values as string.
+ * false to try and convert XML string values into a JSON value.
+ * @param cDataTagName null to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ * @param convertNilAttributeToNull true to parse values with attribute xsi:nil="true" as null.
+ * false to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
+ * @deprecated This constructor has been deprecated in favor of using the new builder
+ * pattern for the configuration.
+ * This constructor may be removed or marked private in a future release.
+ */
+ @Deprecated
+ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = convertNilAttributeToNull;
+ }
+
+ /**
+ * Configure the parser to use custom settings.
+ * @param keepStrings true to parse all values as string.
+ * false to try and convert XML string values into a JSON value.
+ * @param cDataTagName null to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ * @param convertNilAttributeToNull true to parse values with attribute xsi:nil="true" as null.
+ * false to parse values with attribute xsi:nil="true" as {"xsi:nil":true}.
+ * @param xsiTypeMap new HashMap>() to parse values with attribute
+ * xsi:type="integer" as integer, xsi:type="string" as string
+ * @param forceList new HashSet() to parse the provided tags' values as arrays
+ * @param maxNestingDepth int to limit the nesting depth
+ */
+ private XMLParserConfiguration (final boolean keepStrings, final String cDataTagName,
+ final boolean convertNilAttributeToNull, final Map> xsiTypeMap, final Set forceList,
+ final int maxNestingDepth) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = convertNilAttributeToNull;
+ this.xsiTypeMap = Collections.unmodifiableMap(xsiTypeMap);
+ this.forceList = Collections.unmodifiableSet(forceList);
+ this.maxNestingDepth = maxNestingDepth;
+ }
+
+ /**
+ * Provides a new instance of the same configuration.
+ */
+ @Override
+ protected XMLParserConfiguration clone() {
+ // future modifications to this method should always ensure a "deep"
+ // clone in the case of collections. i.e. if a Map is added as a configuration
+ // item, a new map instance should be created and if possible each value in the
+ // map should be cloned as well. If the values of the map are known to also
+ // be immutable, then a shallow clone of the map is acceptable.
+ return new XMLParserConfiguration(
+ this.keepStrings,
+ this.cDataTagName,
+ this.convertNilAttributeToNull,
+ this.xsiTypeMap,
+ this.forceList,
+ this.maxNestingDepth
+ );
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @return The keepStrings configuration value.
+ */
+ public boolean isKeepStrings() {
+ return this.keepStrings;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if values should be kept as strings (true), or if
+ * they should try to be guessed into JSON values (numeric, boolean, string)
+ *
+ * @param newVal
+ * new value to use for the keepStrings configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withKeepStrings(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.keepStrings = newVal;
+ return newConfig;
+ }
+
+ /**
+ * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+ * been the value "content" but can be changed. Use null to indicate no CDATA
+ * processing.
+ *
+ * @return The cDataTagName configuration value.
+ */
+ public String getcDataTagName() {
+ return this.cDataTagName;
+ }
+
+ /**
+ * The name of the key in a JSON Object that indicates a CDATA section. Historically this has
+ * been the value "content" but can be changed. Use null to indicate no CDATA
+ * processing.
+ *
+ * @param newVal
+ * new value to use for the cDataTagName configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withcDataTagName(final String newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.cDataTagName = newVal;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+ * should be kept as attribute(false), or they should be converted to
+ * null(true)
+ *
+ * @return The convertNilAttributeToNull configuration value.
+ */
+ public boolean isConvertNilAttributeToNull() {
+ return this.convertNilAttributeToNull;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies if values with attribute xsi:nil="true"
+ * should be kept as attribute(false), or they should be converted to
+ * null(true)
+ *
+ * @param newVal
+ * new value to use for the convertNilAttributeToNull configuration option.
+ *
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withConvertNilAttributeToNull(final boolean newVal) {
+ XMLParserConfiguration newConfig = this.clone();
+ newConfig.convertNilAttributeToNull = newVal;
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+ * will be converted to target type defined to client in this configuration
+ * {@code Map>} to parse values with attribute
+ * xsi:type="integer" as integer, xsi:type="string" as string
+ * @return xsiTypeMap unmodifiable configuration map.
+ */
+ public Map> getXsiTypeMap() {
+ return this.xsiTypeMap;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that the values with attribute xsi:type
+ * will be converted to target type defined to client in this configuration
+ * {@code Map>} to parse values with attribute
+ * xsi:type="integer" as integer, xsi:type="string" as string
+ * @param xsiTypeMap {@code new HashMap>()} to parse values with attribute
+ * xsi:type="integer" as integer, xsi:type="string" as string
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withXsiTypeMap(final Map> xsiTypeMap) {
+ XMLParserConfiguration newConfig = this.clone();
+ Map> cloneXsiTypeMap = new HashMap>(xsiTypeMap);
+ newConfig.xsiTypeMap = Collections.unmodifiableMap(cloneXsiTypeMap);
+ return newConfig;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that tags that will be converted to arrays
+ * in this configuration {@code Set} to parse the provided tags' values as arrays
+ * @return forceList unmodifiable configuration set.
+ */
+ public Set getForceList() {
+ return this.forceList;
+ }
+
+ /**
+ * When parsing the XML into JSON, specifies that tags that will be converted to arrays
+ * in this configuration {@code Set} to parse the provided tags' values as arrays
+ * @param forceList {@code new HashSet()} to parse the provided tags' values as arrays
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withForceList(final Set forceList) {
+ XMLParserConfiguration newConfig = this.clone();
+ Set cloneForceList = new HashSet(forceList);
+ newConfig.forceList = Collections.unmodifiableSet(cloneForceList);
+ return newConfig;
+ }
+
+ /**
+ * The maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSON.
+ * @return the maximum nesting depth set for this configuration
+ */
+ public int getMaxNestingDepth() {
+ return maxNestingDepth;
+ }
+
+ /**
+ * Defines the maximum nesting depth that the parser will descend before throwing an exception
+ * when parsing the XML into JSON. The default max nesting depth is 512, which means the parser
+ * will throw a JsonException if the maximum depth is reached.
+ * Using any negative value as a parameter is equivalent to setting no limit to the nesting depth,
+ * which means the parses will go as deep as the maximum call stack size allows.
+ * @param maxNestingDepth the maximum nesting depth allowed to the XML parser
+ * @return The existing configuration will not be modified. A new configuration is returned.
+ */
+ public XMLParserConfiguration withMaxNestingDepth(int maxNestingDepth) {
+ XMLParserConfiguration newConfig = this.clone();
+
+ if (maxNestingDepth > UNDEFINED_MAXIMUM_NESTING_DEPTH) {
+ newConfig.maxNestingDepth = maxNestingDepth;
+ } else {
+ newConfig.maxNestingDepth = UNDEFINED_MAXIMUM_NESTING_DEPTH;
+ }
+
+ return newConfig;
+ }
+}
diff --git a/common/src/main/java/org/json/XMLTokener.java b/common/src/main/java/org/json/XMLTokener.java
new file mode 100644
index 0000000..957498c
--- /dev/null
+++ b/common/src/main/java/org/json/XMLTokener.java
@@ -0,0 +1,396 @@
+package org.json;
+
+/*
+Public Domain.
+*/
+
+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 {@code '<' } character which begins a markup
+ * tag, and the content
+ * text between markup tags.
+ *
+ * @return A string, or a {@code '<' } Character, or null if
+ * there is no more source text.
+ * @throws JSONException if a called function has an error
+ */
+ 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();
+ }
+ }
+
+
+ /**
+ * {@code
+ * 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(@SuppressWarnings("unused") 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);
+ }
+
+ /**
+ * Unescape 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' || 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();
+ }
+
+
+ /**
+ * {@code
+ * Returns the next XML meta token. This is used for skipping over
+ * and ...?> structures.
+ * }
+ * @return {@code 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:
+ throw syntaxError("Unterminated string");
+ case '<':
+ case '>':
+ case '/':
+ case '=':
+ case '!':
+ case '?':
+ case '"':
+ case '\'':
+ back();
+ return Boolean.TRUE;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * {@code
+ * 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;
+ }
+ }
+ }
+}
diff --git a/common/src/main/java/org/json/XMLXsiTypeConverter.java b/common/src/main/java/org/json/XMLXsiTypeConverter.java
new file mode 100644
index 0000000..0011eff
--- /dev/null
+++ b/common/src/main/java/org/json/XMLXsiTypeConverter.java
@@ -0,0 +1,46 @@
+package org.json;
+/*
+Public Domain.
+*/
+
+/**
+ * Type conversion configuration interface to be used with xsi:type attributes.
+ *
+ * XML Sample
+ * {@code
+ *
+ * 12345
+ * 54321
+ *
+ * }
+ * JSON Output
+ * {@code
+ * {
+ * "root" : {
+ * "asString" : "12345",
+ * "asInt": 54321
+ * }
+ * }
+ * }
+ *
+ * Usage
+ * {@code
+ * Map> xsiTypeMap = new HashMap>();
+ * xsiTypeMap.put("string", new XMLXsiTypeConverter() {
+ * @Override public String convert(final String value) {
+ * return value;
+ * }
+ * });
+ * xsiTypeMap.put("integer", new XMLXsiTypeConverter() {
+ * @Override public Integer convert(final String value) {
+ * return Integer.valueOf(value);
+ * }
+ * });
+ * }
+ *
+ * @author kumar529
+ * @param return type of convert method
+ */
+public interface XMLXsiTypeConverter {
+ T convert(String value);
+}