entry : this.entrySet()) {
+ String name = entry.getKey();
+ Object valueThis = entry.getValue();
+ Object valueOther = ((JSONObject)other).get(name);
+ if(valueThis == valueOther) {
+ continue;
+ }
+ if(valueThis == null) {
+ return false;
+ }
+ if (valueThis instanceof JSONObject) {
+ if (!((JSONObject)valueThis).similar(valueOther)) {
+ return false;
+ }
+ } else if (valueThis instanceof JSONArray) {
+ if (!((JSONArray)valueThis).similar(valueOther)) {
+ return false;
+ }
+ } else if (!valueThis.equals(valueOther)) {
+ return false;
+ }
+ }
+ return true;
+ } catch (Throwable exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Tests if the value should be tried as a decimal. It makes no test if there are actual digits.
+ *
+ * @param val value to test
+ * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false otherwise.
+ */
+ protected static boolean isDecimalNotation(final String val) {
+ return val.indexOf('.') > -1 || val.indexOf('e') > -1
+ || val.indexOf('E') > -1 || "-0".equals(val);
+ }
+
+ /**
+ * Converts a string to a number using the narrowest possible type. Possible
+ * returns for this function are BigDecimal, Double, BigInteger, Long, and Integer.
+ * When a Double is returned, it should always be a valid Double and not NaN or +-infinity.
+ *
+ * @param val value to convert
+ * @return Number representation of the value.
+ * @throws NumberFormatException thrown if the value is not a valid number. A public
+ * caller should catch this and wrap it in a {@link JSONException} if applicable.
+ */
+ protected static Number stringToNumber(final String val) throws NumberFormatException {
+ char initial = val.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ // quick dirty way to see if we need a BigDecimal instead of a Double
+ // this only handles some cases of overflow or underflow
+ if (val.length()>14) {
+ return new BigDecimal(val);
+ }
+ final Double d = Double.valueOf(val);
+ if (d.isInfinite() || d.isNaN()) {
+ // if we can't parse it as a double, go up to BigDecimal
+ // this is probably due to underflow like 4.32e-678
+ // or overflow like 4.65e5324. The size of the string is small
+ // but can't be held in a Double.
+ return new BigDecimal(val);
+ }
+ return d;
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // string version
+ // The compare string length method reduces GC,
+ // but leads to smaller integers being placed in larger wrappers even though not
+ // needed. i.e. 1,000,000,000 -> Long even though it's an Integer
+ // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
+ //if(val.length()<=9){
+ // return Integer.valueOf(val);
+ //}
+ //if(val.length()<=18){
+ // return Long.valueOf(val);
+ //}
+ //return new BigInteger(val);
+
+ // BigInteger version: We use a similar bitLenth compare as
+ // BigInteger#intValueExact uses. Increases GC, but objects hold
+ // only what they need. i.e. Less runtime overhead if the value is
+ // long lived. Which is the better tradeoff? This is closer to what's
+ // in stringToValue.
+ BigInteger bi = new BigInteger(val);
+ if(bi.bitLength()<=31){
+ return Integer.valueOf(bi.intValue());
+ }
+ if(bi.bitLength()<=63){
+ return Long.valueOf(bi.longValue());
+ }
+ return bi;
+ }
+ throw new NumberFormatException("val ["+val+"] is not a valid number.");
+ }
+
+ /**
+ * Try to convert a string into a number, boolean, or null. If the string
+ * can't be converted, return the string.
+ *
+ * @param string
+ * A String. 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 {
+ // if we want full Big Number support the contents of this
+ // `try` block can be replaced with:
+ // return stringToNumber(string);
+ if (isDecimalNotation(string)) {
+ Double d = Double.valueOf(string);
+ if (!d.isInfinite() && !d.isNaN()) {
+ return d;
+ }
+ } else {
+ Long myLong = Long.valueOf(string);
+ if (string.equals(myLong.toString())) {
+ if (myLong.longValue() == myLong.intValue()) {
+ return Integer.valueOf(myLong.intValue());
+ }
+ return myLong;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Throw an exception if the object is a NaN or infinite number.
+ *
+ * @param o
+ * The object to test.
+ * @throws JSONException
+ * If o is a non-finite number.
+ */
+ public static void testValidity(Object o) throws JSONException {
+ if (o != null) {
+ if (o instanceof Double) {
+ if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
+ throw new JSONException(
+ "JSON does not allow non-finite numbers.");
+ }
+ } else if (o instanceof Float) {
+ if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
+ throw new JSONException(
+ "JSON does not allow non-finite numbers.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Produce a JSONArray containing the values of the members of this
+ * JSONObject.
+ *
+ * @param names
+ * A JSONArray containing a list of key strings. This determines
+ * the sequence of the values in the result.
+ * @return A JSONArray of values.
+ * @throws JSONException
+ * If any of the values are non-finite numbers.
+ */
+ public JSONArray toJSONArray(JSONArray names) throws JSONException {
+ if (names == null || names.isEmpty()) {
+ return null;
+ }
+ JSONArray ja = new JSONArray();
+ for (int i = 0; i < names.length(); i += 1) {
+ ja.put(this.opt(names.getString(i)));
+ }
+ return ja;
+ }
+
+ /**
+ * Make a JSON text of this JSONObject. For compactness, no whitespace is
+ * added. If this would not result in a syntactically correct JSON text,
+ * then null will be returned instead.
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ *
+ * @return a printable, displayable, portable, transmittable representation
+ * of the object, beginning with {
(left
+ * brace) and ending with }
(right
+ * brace).
+ */
+ @Override
+ public String toString() {
+ try {
+ return this.toString(0);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Make a pretty-printed JSON text of this JSONObject.
+ *
+ *
If indentFactor > 0
and the {@link JSONObject}
+ * has only one key, then the object will be output on a single line:
+ *
{@code {"key": 1}}
+ *
+ * If an object has 2 or more keys, then it will be output across
+ * multiple lines: {
+ * "key1": 1,
+ * "key2": "value 2",
+ * "key3": 3
+ * }
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ *
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @return a printable, displayable, portable, transmittable representation
+ * of the object, beginning with {
(left
+ * brace) and ending with }
(right
+ * brace).
+ * @throws JSONException
+ * If the object contains an invalid number.
+ */
+ public String toString(int indentFactor) throws JSONException {
+ StringWriter w = new StringWriter();
+ synchronized (w.getBuffer()) {
+ return this.write(w, indentFactor, 0).toString();
+ }
+ }
+
+ /**
+ * Make a JSON text of an Object value. If the object has an
+ * value.toJSONString() method, then that method will be used to produce the
+ * JSON text. The method is required to produce a strictly conforming text.
+ * If the object does not contain a toJSONString method (which is the most
+ * common case), then a text will be produced by other means. If the value
+ * is an array or Collection, then a JSONArray will be made from it and its
+ * toJSONString method will be called. If the value is a MAP, then a
+ * JSONObject will be made from it and its toJSONString method will be
+ * called. Otherwise, the value's toString method will be called, and the
+ * result will be quoted.
+ *
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @param value
+ * The value to be serialized.
+ * @return a printable, displayable, transmittable representation of the
+ * object, beginning with {
(left
+ * brace) and ending with }
(right
+ * brace).
+ * @throws JSONException
+ * If the value is or contains an invalid number.
+ */
+ public static String valueToString(Object value) throws JSONException {
+ // moves the implementation to JSONWriter as:
+ // 1. It makes more sense to be part of the writer class
+ // 2. For Android support this method is not available. By implementing it in the Writer
+ // Android users can use the writer with the built in Android JSONObject implementation.
+ return JSONWriter.valueToString(value);
+ }
+
+ /**
+ * Wrap an object, if necessary. If the object is null
, return the NULL
+ * object. If it is an array or collection, wrap it in a JSONArray. If it is
+ * a map, wrap it in a JSONObject. If it is a standard property (Double,
+ * String, et al) then it is already wrapped. Otherwise, if it comes from
+ * one of the java packages, turn it into a string. And if it doesn't, try
+ * to wrap it in a JSONObject. If the wrapping fails, then null is returned.
+ *
+ * @param object
+ * The object to wrap
+ * @return The wrapped value
+ */
+ public static Object wrap(Object object) {
+ try {
+ if (object == null) {
+ return NULL;
+ }
+ if (object instanceof JSONObject || object instanceof JSONArray
+ || NULL.equals(object) || object instanceof JSONString
+ || object instanceof Byte || object instanceof Character
+ || object instanceof Short || object instanceof Integer
+ || object instanceof Long || object instanceof Boolean
+ || object instanceof Float || object instanceof Double
+ || object instanceof String || object instanceof BigInteger
+ || object instanceof BigDecimal || object instanceof Enum) {
+ return object;
+ }
+
+ if (object instanceof Collection) {
+ Collection> coll = (Collection>) object;
+ return new JSONArray(coll);
+ }
+ if (object.getClass().isArray()) {
+ return new JSONArray(object);
+ }
+ if (object instanceof Map) {
+ Map, ?> map = (Map, ?>) object;
+ return new JSONObject(map);
+ }
+ Package objectPackage = object.getClass().getPackage();
+ String objectPackageName = objectPackage != null ? objectPackage
+ .getName() : "";
+ if (objectPackageName.startsWith("java.")
+ || objectPackageName.startsWith("javax.")
+ || object.getClass().getClassLoader() == null) {
+ return object.toString();
+ }
+ return new JSONObject(object);
+ } catch (Exception exception) {
+ return null;
+ }
+ }
+
+ /**
+ * Write the contents of the JSONObject as JSON text to a writer. For
+ * compactness, no whitespace is added.
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ *
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer) throws JSONException {
+ return this.write(writer, 0, 0);
+ }
+
+ static final Writer writeValue(Writer writer, Object value,
+ int indentFactor, int indent) throws JSONException, IOException {
+ if (value == null || value.equals(null)) {
+ writer.write("null");
+ } else if (value instanceof JSONString) {
+ Object o;
+ try {
+ o = ((JSONString) value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ writer.write(o != null ? o.toString() : quote(value.toString()));
+ } else if (value instanceof Number) {
+ // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
+ final String numberAsString = numberToString((Number) value);
+ 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 indentFactor > 0
and the {@link JSONObject}
+ * has only one key, then the object will be output on a single line:
+ *
{@code {"key": 1}}
+ *
+ * If an object has 2 or more keys, then it will be output across
+ * multiple lines: {
+ * "key1": 1,
+ * "key2": "value 2",
+ * "key3": 3
+ * }
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ *
+ * @param writer
+ * Writes the serialized JSON
+ * @param indentFactor
+ * The number of spaces to add to each level of indentation.
+ * @param indent
+ * The indentation of the top level.
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer, int indentFactor, int indent)
+ throws JSONException {
+ try {
+ boolean commanate = false;
+ final int length = this.length();
+ writer.write('{');
+
+ if (length == 1) {
+ final Entry entry = this.entrySet().iterator().next();
+ final String key = entry.getKey();
+ writer.write(quote(key));
+ writer.write(':');
+ if (indentFactor > 0) {
+ writer.write(' ');
+ }
+ try{
+ writeValue(writer, entry.getValue(), indentFactor, indent);
+ } catch (Exception e) {
+ throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+ }
+ } else if (length != 0) {
+ final int newindent = indent + indentFactor;
+ for (final Entry entry : this.entrySet()) {
+ if (commanate) {
+ writer.write(',');
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, newindent);
+ final String key = entry.getKey();
+ writer.write(quote(key));
+ writer.write(':');
+ if (indentFactor > 0) {
+ writer.write(' ');
+ }
+ try {
+ writeValue(writer, entry.getValue(), indentFactor, newindent);
+ } catch (Exception e) {
+ throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+ }
+ commanate = true;
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, indent);
+ }
+ writer.write('}');
+ return writer;
+ } catch (IOException exception) {
+ throw new JSONException(exception);
+ }
+ }
+
+ /**
+ * Returns a java.util.Map containing all of the entries in this object.
+ * If an entry in the object is a JSONArray or JSONObject it will also
+ * be converted.
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @return a java.util.Map containing the entries of this object
+ */
+ public Map toMap() {
+ Map results = new HashMap();
+ for (Entry entry : this.entrySet()) {
+ Object value;
+ if (entry.getValue() == null || NULL.equals(entry.getValue())) {
+ value = null;
+ } else if (entry.getValue() instanceof JSONObject) {
+ value = ((JSONObject) entry.getValue()).toMap();
+ } else if (entry.getValue() instanceof JSONArray) {
+ value = ((JSONArray) entry.getValue()).toList();
+ } else {
+ value = entry.getValue();
+ }
+ results.put(entry.getKey(), value);
+ }
+ return results;
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONPointer.java b/src/main/java/ch/m4th1eu/json/JSONPointer.java
new file mode 100644
index 0000000..2176655
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONPointer.java
@@ -0,0 +1,293 @@
+package ch.m4th1eu.json;
+
+import static java.lang.String.format;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * A JSON Pointer is a simple query language defined for JSON documents by
+ * RFC 6901.
+ *
+ * In a nutshell, JSONPointer allows the user to navigate into a JSON document
+ * using strings, and retrieve targeted objects, like a simple form of XPATH.
+ * Path segments are separated by the '/' char, which signifies the root of
+ * the document when it appears as the first char of the string. Array
+ * elements are navigated using ordinals, counting from 0. JSONPointer strings
+ * may be extended to any arbitrary number of segments. If the navigation
+ * is successful, the matched item is returned. A matched item may be a
+ * JSONObject, a JSONArray, or a JSON value. If the JSONPointer string building
+ * fails, an appropriate exception is thrown. If the navigation fails to find
+ * a match, a JSONPointerException is thrown.
+ *
+ * @author JSON.org
+ * @version 2016-05-14
+ */
+public class JSONPointer {
+
+ // used for URL encoding and decoding
+ private static final String ENCODING = "utf-8";
+
+ /**
+ * This class allows the user to build a JSONPointer in steps, using
+ * exactly one segment in each step.
+ */
+ public static class Builder {
+
+ // Segments for the eventual JSONPointer string
+ private final List refTokens = new ArrayList();
+
+ /**
+ * Creates a {@code JSONPointer} instance using the tokens previously set using the
+ * {@link #append(String)} method calls.
+ */
+ public JSONPointer build() {
+ return new JSONPointer(this.refTokens);
+ }
+
+ /**
+ * Adds an arbitrary token to the list of reference tokens. It can be any non-null value.
+ *
+ * Unlike in the case of JSON string or URI fragment representation of JSON pointers, the
+ * argument of this method MUST NOT be escaped. If you want to query the property called
+ * {@code "a~b"} then you should simply pass the {@code "a~b"} string as-is, there is no
+ * need to escape it as {@code "a~0b"}.
+ *
+ * @param token the new token to be appended to the list
+ * @return {@code this}
+ * @throws NullPointerException if {@code token} is null
+ */
+ public Builder append(String token) {
+ if (token == null) {
+ throw new NullPointerException("token cannot be null");
+ }
+ this.refTokens.add(token);
+ return this;
+ }
+
+ /**
+ * Adds an integer to the reference token list. Although not necessarily, mostly this token will
+ * denote an array index.
+ *
+ * @param arrayIndex the array index to be added to the token list
+ * @return {@code this}
+ */
+ public Builder append(int arrayIndex) {
+ this.refTokens.add(String.valueOf(arrayIndex));
+ return this;
+ }
+ }
+
+ /**
+ * Static factory method for {@link Builder}. Example usage:
+ *
+ *
+ * JSONPointer pointer = JSONPointer.builder()
+ * .append("obj")
+ * .append("other~key").append("another/key")
+ * .append("\"")
+ * .append(0)
+ * .build();
+ *
+ *
+ * @return a builder instance which can be used to construct a {@code JSONPointer} instance by chained
+ * {@link Builder#append(String)} calls.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ // Segments for the JSONPointer string
+ private final List refTokens;
+
+ /**
+ * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to
+ * evaluate the same JSON Pointer on different JSON documents then it is recommended
+ * to keep the {@code JSONPointer} instances due to performance considerations.
+ *
+ * @param pointer the JSON String or URI Fragment representation of the JSON pointer.
+ * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer
+ */
+ public JSONPointer(final String pointer) {
+ if (pointer == null) {
+ throw new NullPointerException("pointer cannot be null");
+ }
+ if (pointer.isEmpty() || pointer.equals("#")) {
+ this.refTokens = Collections.emptyList();
+ return;
+ }
+ String refs;
+ if (pointer.startsWith("#/")) {
+ refs = pointer.substring(2);
+ try {
+ refs = URLDecoder.decode(refs, ENCODING);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ } else if (pointer.startsWith("/")) {
+ refs = pointer.substring(1);
+ } else {
+ throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'");
+ }
+ this.refTokens = new ArrayList();
+ int slashIdx = -1;
+ int prevSlashIdx = 0;
+ do {
+ prevSlashIdx = slashIdx + 1;
+ slashIdx = refs.indexOf('/', prevSlashIdx);
+ if(prevSlashIdx == slashIdx || prevSlashIdx == refs.length()) {
+ // found 2 slashes in a row ( obj//next )
+ // or single slash at the end of a string ( obj/test/ )
+ this.refTokens.add("");
+ } else if (slashIdx >= 0) {
+ final String token = refs.substring(prevSlashIdx, slashIdx);
+ this.refTokens.add(unescape(token));
+ } else {
+ // last item after separator, or no separator at all.
+ final String token = refs.substring(prevSlashIdx);
+ this.refTokens.add(unescape(token));
+ }
+ } while (slashIdx >= 0);
+ // using split does not take into account consecutive separators or "ending nulls"
+ //for (String token : refs.split("/")) {
+ // this.refTokens.add(unescape(token));
+ //}
+ }
+
+ public JSONPointer(List refTokens) {
+ this.refTokens = new ArrayList(refTokens);
+ }
+
+ private String unescape(String token) {
+ return token.replace("~1", "/").replace("~0", "~")
+ .replace("\\\"", "\"")
+ .replace("\\\\", "\\");
+ }
+
+ /**
+ * Evaluates this JSON Pointer on the given {@code document}. The {@code document}
+ * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty
+ * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the
+ * returned value will be {@code document} itself.
+ *
+ * @param document the JSON document which should be the subject of querying.
+ * @return the result of the evaluation
+ * @throws JSONPointerException if an error occurs during evaluation
+ */
+ public Object queryFrom(Object document) throws JSONPointerException {
+ if (this.refTokens.isEmpty()) {
+ return document;
+ }
+ Object current = document;
+ for (String token : this.refTokens) {
+ if (current instanceof JSONObject) {
+ current = ((JSONObject) current).opt(unescape(token));
+ } else if (current instanceof JSONArray) {
+ current = readByIndexToken(current, token);
+ } else {
+ throw new JSONPointerException(format(
+ "value [%s] is not an array or object therefore its key %s cannot be resolved", current,
+ token));
+ }
+ }
+ return current;
+ }
+
+ /**
+ * Matches a JSONArray element by ordinal position
+ * @param current the JSONArray to be evaluated
+ * @param indexToken the array index in string form
+ * @return the matched object. If no matching item is found a
+ * @throws JSONPointerException is thrown if the index is out of bounds
+ */
+ private Object readByIndexToken(Object current, String indexToken) throws JSONPointerException {
+ try {
+ int index = Integer.parseInt(indexToken);
+ JSONArray currentArr = (JSONArray) current;
+ if (index >= currentArr.length()) {
+ throw new JSONPointerException(format("index %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. Backslashes
+ * and double quote chars are also escaped.
+ * @param token the JSONPointer segment value to be escaped
+ * @return the escaped value for the token
+ */
+ private String escape(String token) {
+ return token.replace("~", "~0")
+ .replace("/", "~1")
+ .replace("\\", "\\\\")
+ .replace("\"", "\\\"");
+ }
+
+ /**
+ * Returns a string representing the JSONPointer path value using URI
+ * fragment identifier representation
+ */
+ public String toURIFragment() {
+ try {
+ StringBuilder rval = new StringBuilder("#");
+ for (String token : this.refTokens) {
+ rval.append('/').append(URLEncoder.encode(token, ENCODING));
+ }
+ return rval.toString();
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONPointerException.java b/src/main/java/ch/m4th1eu/json/JSONPointerException.java
new file mode 100644
index 0000000..c16ba1b
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONPointerException.java
@@ -0,0 +1,45 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs
+ * during evaluating a pointer.
+ *
+ * @author JSON.org
+ * @version 2016-05-13
+ */
+public class JSONPointerException extends JSONException {
+ private static final long serialVersionUID = 8872944667561856751L;
+
+ public JSONPointerException(String message) {
+ super(message);
+ }
+
+ public JSONPointerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONPropertyIgnore.java b/src/main/java/ch/m4th1eu/json/JSONPropertyIgnore.java
new file mode 100644
index 0000000..0e9271c
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONPropertyIgnore.java
@@ -0,0 +1,43 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -> JSONObject mapping. If this annotation is
+ * present at any level in the class hierarchy, then the method will
+ * not be serialized from the bean into the JSONObject.
+ */
+public @interface JSONPropertyIgnore { }
diff --git a/src/main/java/ch/m4th1eu/json/JSONPropertyName.java b/src/main/java/ch/m4th1eu/json/JSONPropertyName.java
new file mode 100644
index 0000000..650df18
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONPropertyName.java
@@ -0,0 +1,47 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2018 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RUNTIME)
+@Target({METHOD})
+/**
+ * Use this annotation on a getter method to override the Bean name
+ * parser for Bean -> JSONObject mapping. A value set to empty string ""
+ * will have the Bean parser fall back to the default field name processing.
+ */
+public @interface JSONPropertyName {
+ /**
+ * @return The name of the property as to be used in the JSON Object.
+ */
+ String value();
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONString.java b/src/main/java/ch/m4th1eu/json/JSONString.java
new file mode 100644
index 0000000..384164b
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONString.java
@@ -0,0 +1,18 @@
+package ch.m4th1eu.json;
+/**
+ * The JSONString
interface allows a toJSONString()
+ * method so that a class can change the behavior of
+ * JSONObject.toString()
, JSONArray.toString()
,
+ * and JSONWriter.value(
Object)
. The
+ * toJSONString
method will be used instead of the default behavior
+ * of using the Object's toString()
method and quoting the result.
+ */
+public interface JSONString {
+ /**
+ * The toJSONString
method allows a class to produce its own JSON
+ * serialization.
+ *
+ * @return A strictly syntactically correct JSON text.
+ */
+ public String toJSONString();
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONStringer.java b/src/main/java/ch/m4th1eu/json/JSONStringer.java
new file mode 100644
index 0000000..7a0e5f4
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONStringer.java
@@ -0,0 +1,79 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.StringWriter;
+
+/**
+ * JSONStringer provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONStringer can produce one JSON text.
+ *
+ * A JSONStringer instance provides a value
method for appending
+ * values to the
+ * text, and a key
+ * method for adding keys before values in objects. There are array
+ * and endArray
methods that make and bound array values, and
+ * object
and endObject
methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting cascade style. For example,
+ * myString = new JSONStringer()
+ * .object()
+ * .key("JSON")
+ * .value("Hello, World!")
+ * .endObject()
+ * .toString();
which produces the string
+ * {"JSON":"Hello, World!"}
+ *
+ * The first method called must be array
or object
.
+ * There are no methods for adding commas or colons. JSONStringer adds them for
+ * you. Objects and arrays can be nested up to 20 levels deep.
+ *
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class JSONStringer extends JSONWriter {
+ /**
+ * Make a fresh JSONStringer. It can be used to build one JSON text.
+ */
+ public JSONStringer() {
+ super(new StringWriter());
+ }
+
+ /**
+ * Return the JSON text. This method is used to obtain the product of the
+ * JSONStringer instance. It will return null
if there was a
+ * problem in the construction of the JSON text (such as the calls to
+ * array
were not properly balanced with calls to
+ * endArray
).
+ * @return The JSON text.
+ */
+ @Override
+ public String toString() {
+ return this.mode == 'd' ? this.writer.toString() : null;
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/JSONTokener.java b/src/main/java/ch/m4th1eu/json/JSONTokener.java
new file mode 100644
index 0000000..e14d2cd
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONTokener.java
@@ -0,0 +1,531 @@
+package ch.m4th1eu.json;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+/**
+ * A JSONTokener takes a source string and extracts characters and tokens from
+ * it. It is used by the JSONObject and JSONArray constructors to parse
+ * JSON source strings.
+ * @author JSON.org
+ * @version 2014-05-03
+ */
+public class JSONTokener {
+ /** current read character position on the current line. */
+ private long character;
+ /** flag to indicate if the end of the input has been found. */
+ private boolean eof;
+ /** current read index of the input. */
+ private long index;
+ /** current line of the input. */
+ private long line;
+ /** previous character read from the input. */
+ private char previous;
+ /** Reader for the input. */
+ private final Reader reader;
+ /** flag to indicate that a previous character was requested. */
+ private boolean usePrevious;
+ /** the number of characters read in the previous line. */
+ private long characterPreviousLine;
+
+
+ /**
+ * Construct a JSONTokener from a Reader. The caller must close the Reader.
+ *
+ * @param reader A reader.
+ */
+ public JSONTokener(Reader reader) {
+ this.reader = reader.markSupported()
+ ? reader
+ : new BufferedReader(reader);
+ this.eof = false;
+ this.usePrevious = false;
+ this.previous = 0;
+ this.index = 0;
+ this.character = 1;
+ this.characterPreviousLine = 0;
+ this.line = 1;
+ }
+
+
+ /**
+ * Construct a JSONTokener from an InputStream. The caller must close the input stream.
+ * @param inputStream The source.
+ */
+ public JSONTokener(InputStream inputStream) {
+ this(new InputStreamReader(inputStream));
+ }
+
+
+ /**
+ * Construct a JSONTokener from a string.
+ *
+ * @param s A source string.
+ */
+ public JSONTokener(String s) {
+ this(new StringReader(s));
+ }
+
+
+ /**
+ * Back up one character. This provides a sort of lookahead capability,
+ * so that you can test for a digit or letter before attempting to parse
+ * the next number or identifier.
+ * @throws JSONException Thrown if trying to step back more than 1 step
+ * or if already at the start of the string
+ */
+ public void back() throws JSONException {
+ if (this.usePrevious || this.index <= 0) {
+ throw new JSONException("Stepping back two steps is not supported");
+ }
+ this.decrementIndexes();
+ this.usePrevious = true;
+ this.eof = false;
+ }
+
+ /**
+ * Decrements the indexes for the {@link #back()} method based on the previous character read.
+ */
+ private void decrementIndexes() {
+ this.index--;
+ if(this.previous=='\r' || this.previous == '\n') {
+ this.line--;
+ this.character=this.characterPreviousLine ;
+ } else if(this.character > 0){
+ this.character--;
+ }
+ }
+
+ /**
+ * Get the hex value of a character (base16).
+ * @param c A character between '0' and '9' or between 'A' and 'F' or
+ * between 'a' and 'f'.
+ * @return An int between 0 and 15, or -1 if c was not a hex digit.
+ */
+ public static int dehexchar(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ if (c >= 'A' && c <= 'F') {
+ return c - ('A' - 10);
+ }
+ if (c >= 'a' && c <= 'f') {
+ return c - ('a' - 10);
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if the end of the input has been reached.
+ *
+ * @return true if at the end of the file and we didn't step back
+ */
+ public boolean end() {
+ return this.eof && !this.usePrevious;
+ }
+
+
+ /**
+ * Determine if the source string still contains characters that next()
+ * can consume.
+ * @return true if not yet at the end of the source.
+ * @throws JSONException thrown if there is an error stepping forward
+ * or backward while checking for more data.
+ */
+ public boolean more() throws JSONException {
+ if(this.usePrevious) {
+ return true;
+ }
+ try {
+ this.reader.mark(1);
+ } catch (IOException e) {
+ throw new JSONException("Unable to preserve stream position", e);
+ }
+ try {
+ // -1 is EOF, but next() can not consume the null character '\0'
+ if(this.reader.read() <= 0) {
+ this.eof = true;
+ return false;
+ }
+ this.reader.reset();
+ } catch (IOException e) {
+ throw new JSONException("Unable to read the next character from the stream", e);
+ }
+ return true;
+ }
+
+
+ /**
+ * Get the next character in the source string.
+ *
+ * @return The next character, or 0 if past the end of the source string.
+ * @throws JSONException Thrown if there is an error reading the source string.
+ */
+ public char next() throws JSONException {
+ int c;
+ if (this.usePrevious) {
+ this.usePrevious = false;
+ c = this.previous;
+ } else {
+ try {
+ c = this.reader.read();
+ } catch (IOException exception) {
+ throw new JSONException(exception);
+ }
+ }
+ if (c <= 0) { // End of stream
+ this.eof = true;
+ return 0;
+ }
+ this.incrementIndexes(c);
+ this.previous = (char) c;
+ return this.previous;
+ }
+
+ /**
+ * Increments the internal indexes according to the previous character
+ * read and the character passed as the current character.
+ * @param c the current character read.
+ */
+ private void incrementIndexes(int c) {
+ if(c > 0) {
+ this.index++;
+ if(c=='\r') {
+ this.line++;
+ this.characterPreviousLine = this.character;
+ this.character=0;
+ }else if (c=='\n') {
+ if(this.previous != '\r') {
+ this.line++;
+ this.characterPreviousLine = this.character;
+ }
+ this.character=0;
+ } else {
+ this.character++;
+ }
+ }
+ }
+
+ /**
+ * Consume the next character, and check that it matches a specified
+ * character.
+ * @param c The character to match.
+ * @return The character.
+ * @throws JSONException if the character does not match.
+ */
+ public char next(char c) throws JSONException {
+ char n = this.next();
+ if (n != c) {
+ if(n > 0) {
+ throw this.syntaxError("Expected '" + c + "' and instead saw '" +
+ n + "'");
+ }
+ throw this.syntaxError("Expected '" + c + "' and instead saw ''");
+ }
+ return n;
+ }
+
+
+ /**
+ * Get the next n characters.
+ *
+ * @param n The number of characters to take.
+ * @return A string of n characters.
+ * @throws JSONException
+ * Substring bounds error if there are not
+ * n characters remaining in the source string.
+ */
+ public String next(int n) throws JSONException {
+ if (n == 0) {
+ return "";
+ }
+
+ char[] chars = new char[n];
+ int pos = 0;
+
+ while (pos < n) {
+ chars[pos] = this.next();
+ if (this.end()) {
+ throw this.syntaxError("Substring bounds error");
+ }
+ pos += 1;
+ }
+ return new String(chars);
+ }
+
+
+ /**
+ * Get the next char in the string, skipping whitespace.
+ * @throws JSONException Thrown if there is an error reading the source string.
+ * @return A character, or 0 if there are no more characters.
+ */
+ public char nextClean() throws JSONException {
+ for (;;) {
+ char c = this.next();
+ if (c == 0 || c > ' ') {
+ return c;
+ }
+ }
+ }
+
+
+ /**
+ * Return the characters up to the next close quote character.
+ * Backslash processing is done. The formal JSON format does not
+ * allow strings in single quotes, but an implementation is allowed to
+ * accept them.
+ * @param quote The quoting character, either
+ * "
(double quote) or
+ * '
(single quote).
+ * @return A String.
+ * @throws JSONException Unterminated string.
+ */
+ public String nextString(char quote) throws JSONException {
+ char c;
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ c = this.next();
+ switch (c) {
+ case 0:
+ case '\n':
+ case '\r':
+ throw this.syntaxError("Unterminated string");
+ case '\\':
+ c = this.next();
+ switch (c) {
+ case 'b':
+ sb.append('\b');
+ break;
+ case 't':
+ sb.append('\t');
+ break;
+ case 'n':
+ sb.append('\n');
+ break;
+ case 'f':
+ sb.append('\f');
+ break;
+ case 'r':
+ sb.append('\r');
+ break;
+ case 'u':
+ try {
+ sb.append((char)Integer.parseInt(this.next(4), 16));
+ } catch (NumberFormatException e) {
+ throw this.syntaxError("Illegal escape.", e);
+ }
+ break;
+ case '"':
+ case '\'':
+ case '\\':
+ case '/':
+ sb.append(c);
+ break;
+ default:
+ throw this.syntaxError("Illegal escape.");
+ }
+ break;
+ default:
+ if (c == quote) {
+ return sb.toString();
+ }
+ sb.append(c);
+ }
+ }
+ }
+
+
+ /**
+ * Get the text up but not including the specified character or the
+ * end of line, whichever comes first.
+ * @param delimiter A delimiter character.
+ * @return A string.
+ * @throws JSONException Thrown if there is an error while searching
+ * for the delimiter
+ */
+ public String nextTo(char delimiter) throws JSONException {
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ char c = this.next();
+ if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
+ if (c != 0) {
+ this.back();
+ }
+ return sb.toString().trim();
+ }
+ sb.append(c);
+ }
+ }
+
+
+ /**
+ * Get the text up but not including one of the specified delimiter
+ * characters or the end of line, whichever comes first.
+ * @param delimiters A set of delimiter characters.
+ * @return A string, trimmed.
+ * @throws JSONException Thrown if there is an error while searching
+ * for the delimiter
+ */
+ public String nextTo(String delimiters) throws JSONException {
+ char c;
+ StringBuilder sb = new StringBuilder();
+ for (;;) {
+ c = this.next();
+ if (delimiters.indexOf(c) >= 0 || c == 0 ||
+ c == '\n' || c == '\r') {
+ if (c != 0) {
+ this.back();
+ }
+ return sb.toString().trim();
+ }
+ sb.append(c);
+ }
+ }
+
+
+ /**
+ * Get the next value. The value can be a Boolean, Double, Integer,
+ * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object.
+ * @throws JSONException If syntax error.
+ *
+ * @return An object.
+ */
+ public Object nextValue() throws JSONException {
+ char c = this.nextClean();
+ String string;
+
+ switch (c) {
+ case '"':
+ case '\'':
+ return this.nextString(c);
+ case '{':
+ this.back();
+ return new JSONObject(this);
+ case '[':
+ this.back();
+ return new JSONArray(this);
+ }
+
+ /*
+ * Handle unquoted text. This could be the values true, false, or
+ * null, or it can be a number. An implementation (such as this one)
+ * is allowed to also accept non-standard forms.
+ *
+ * Accumulate characters until we reach the end of the text or a
+ * formatting character.
+ */
+
+ StringBuilder sb = new StringBuilder();
+ while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) {
+ sb.append(c);
+ c = this.next();
+ }
+ 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/src/main/java/ch/m4th1eu/json/JSONWriter.java b/src/main/java/ch/m4th1eu/json/JSONWriter.java
new file mode 100644
index 0000000..b317357
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/JSONWriter.java
@@ -0,0 +1,413 @@
+package ch.m4th1eu.json;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+
+/*
+Copyright (c) 2006 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * JSONWriter provides a quick and convenient way of producing JSON text.
+ * The texts produced strictly conform to JSON syntax rules. No whitespace is
+ * added, so the results are ready for transmission or storage. Each instance of
+ * JSONWriter can produce one JSON text.
+ *
+ * A JSONWriter instance provides a value
method for appending
+ * values to the
+ * text, and a key
+ * method for adding keys before values in objects. There are array
+ * and endArray
methods that make and bound array values, and
+ * object
and endObject
methods which make and bound
+ * object values. All of these methods return the JSONWriter instance,
+ * permitting a cascade style. For example,
+ * new JSONWriter(myWriter)
+ * .object()
+ * .key("JSON")
+ * .value("Hello, World!")
+ * .endObject();
which writes
+ * {"JSON":"Hello, World!"}
+ *
+ * The first method called must be array
or object
.
+ * There are no methods for adding commas or colons. JSONWriter adds them for
+ * you. Objects and arrays can be nested up to 200 levels deep.
+ *
+ * This can sometimes be easier than using a JSONObject to build a string.
+ * @author JSON.org
+ * @version 2016-08-08
+ */
+public class JSONWriter {
+ private static final int maxdepth = 200;
+
+ /**
+ * The comma flag determines if a comma should be output before the next
+ * value.
+ */
+ private boolean comma;
+
+ /**
+ * The current mode. Values:
+ * 'a' (array),
+ * 'd' (done),
+ * 'i' (initial),
+ * 'k' (key),
+ * 'o' (object).
+ */
+ protected char mode;
+
+ /**
+ * The object/array stack.
+ */
+ private final JSONObject stack[];
+
+ /**
+ * The stack top index. A value of 0 indicates that the stack is empty.
+ */
+ private int top;
+
+ /**
+ * The writer that will receive the output.
+ */
+ protected Appendable writer;
+
+ /**
+ * Make a fresh JSONWriter. It can be used to build one JSON text.
+ */
+ public JSONWriter(Appendable w) {
+ this.comma = false;
+ this.mode = 'i';
+ this.stack = new JSONObject[maxdepth];
+ this.top = 0;
+ this.writer = w;
+ }
+
+ /**
+ * Append a value.
+ * @param string A string value.
+ * @return this
+ * @throws JSONException If the value is out of sequence.
+ */
+ private JSONWriter append(String string) throws JSONException {
+ if (string == null) {
+ throw new JSONException("Null pointer");
+ }
+ if (this.mode == 'o' || this.mode == 'a') {
+ try {
+ if (this.comma && this.mode == 'a') {
+ this.writer.append(',');
+ }
+ this.writer.append(string);
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ if (this.mode == 'o') {
+ this.mode = 'k';
+ }
+ this.comma = true;
+ return this;
+ }
+ throw new JSONException("Value out of sequence.");
+ }
+
+ /**
+ * Begin appending a new array. All values until the balancing
+ * endArray
will be appended to this array. The
+ * endArray
method must be called to mark the array's end.
+ * @return this
+ * @throws JSONException If the nesting is too deep, or if the object is
+ * started in the wrong place (for example as a key or after the end of the
+ * outermost array or object).
+ */
+ public JSONWriter array() throws JSONException {
+ if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') {
+ this.push(null);
+ this.append("[");
+ this.comma = false;
+ return this;
+ }
+ throw new JSONException("Misplaced array.");
+ }
+
+ /**
+ * End something.
+ * @param m Mode
+ * @param c Closing character
+ * @return this
+ * @throws JSONException If unbalanced.
+ */
+ private JSONWriter end(char m, char c) throws JSONException {
+ if (this.mode != m) {
+ throw new JSONException(m == 'a'
+ ? "Misplaced endArray."
+ : "Misplaced endObject.");
+ }
+ this.pop(m);
+ try {
+ this.writer.append(c);
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ this.comma = true;
+ return this;
+ }
+
+ /**
+ * End an array. This method most be called to balance calls to
+ * array
.
+ * @return this
+ * @throws JSONException If incorrectly nested.
+ */
+ public JSONWriter endArray() throws JSONException {
+ return this.end('a', ']');
+ }
+
+ /**
+ * End an object. This method most be called to balance calls to
+ * object
.
+ * @return this
+ * @throws JSONException If incorrectly nested.
+ */
+ public JSONWriter endObject() throws JSONException {
+ return this.end('k', '}');
+ }
+
+ /**
+ * Append a key. The key will be associated with the next value. In an
+ * object, every value must be preceded by a key.
+ * @param string A key string.
+ * @return this
+ * @throws JSONException If the key is out of place. For example, keys
+ * do not belong in arrays or if the key is null.
+ */
+ public JSONWriter key(String string) throws JSONException {
+ if (string == null) {
+ throw new JSONException("Null key.");
+ }
+ if (this.mode == 'k') {
+ try {
+ JSONObject topObject = this.stack[this.top - 1];
+ // don't use the built in putOnce method to maintain Android support
+ if(topObject.has(string)) {
+ throw new JSONException("Duplicate key \"" + string + "\"");
+ }
+ topObject.put(string, true);
+ if (this.comma) {
+ this.writer.append(',');
+ }
+ this.writer.append(JSONObject.quote(string));
+ this.writer.append(':');
+ this.comma = false;
+ this.mode = 'o';
+ return this;
+ } catch (IOException e) {
+ // Android as of API 25 does not support this exception constructor
+ // however we won't worry about it. If an exception is happening here
+ // it will just throw a "Method not found" exception instead.
+ throw new JSONException(e);
+ }
+ }
+ throw new JSONException("Misplaced key.");
+ }
+
+
+ /**
+ * Begin appending a new object. All keys and values until the balancing
+ * endObject
will be appended to this object. The
+ * endObject
method must be called to mark the object's end.
+ * @return this
+ * @throws JSONException If the nesting is too deep, or if the object is
+ * started in the wrong place (for example as a key or after the end of the
+ * outermost array or object).
+ */
+ public JSONWriter object() throws JSONException {
+ if (this.mode == 'i') {
+ this.mode = 'o';
+ }
+ if (this.mode == 'o' || this.mode == 'a') {
+ this.append("{");
+ this.push(new JSONObject());
+ this.comma = false;
+ return this;
+ }
+ throw new JSONException("Misplaced object.");
+
+ }
+
+
+ /**
+ * Pop an array or object scope.
+ * @param c The scope to close.
+ * @throws JSONException If nesting is wrong.
+ */
+ private void pop(char c) throws JSONException {
+ if (this.top <= 0) {
+ throw new JSONException("Nesting error.");
+ }
+ char m = this.stack[this.top - 1] == null ? 'a' : 'k';
+ if (m != c) {
+ throw new JSONException("Nesting error.");
+ }
+ this.top -= 1;
+ this.mode = this.top == 0
+ ? 'd'
+ : this.stack[this.top - 1] == null
+ ? 'a'
+ : 'k';
+ }
+
+ /**
+ * Push an array or object scope.
+ * @param jo The scope to open.
+ * @throws JSONException If nesting is too deep.
+ */
+ private void push(JSONObject jo) throws JSONException {
+ if (this.top >= maxdepth) {
+ throw new JSONException("Nesting too deep.");
+ }
+ this.stack[this.top] = jo;
+ this.mode = jo == null ? 'a' : 'k';
+ this.top += 1;
+ }
+
+ /**
+ * Make a JSON text of an Object value. If the object has an
+ * value.toJSONString() method, then that method will be used to produce the
+ * JSON text. The method is required to produce a strictly conforming text.
+ * If the object does not contain a toJSONString method (which is the most
+ * common case), then a text will be produced by other means. If the value
+ * is an array or Collection, then a JSONArray will be made from it and its
+ * toJSONString method will be called. If the value is a MAP, then a
+ * JSONObject will be made from it and its toJSONString method will be
+ * called. Otherwise, the value's toString method will be called, and the
+ * result will be quoted.
+ *
+ *
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @param value
+ * The value to be serialized.
+ * @return a printable, displayable, transmittable representation of the
+ * object, beginning with {
(left
+ * brace) and ending with }
(right
+ * brace).
+ * @throws JSONException
+ * If the value is or contains an invalid number.
+ */
+ public static String valueToString(Object value) throws JSONException {
+ if (value == null || value.equals(null)) {
+ return "null";
+ }
+ if (value instanceof JSONString) {
+ 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
+ */
+ public JSONWriter value(boolean b) throws JSONException {
+ return this.append(b ? "true" : "false");
+ }
+
+ /**
+ * Append a double value.
+ * @param d A double.
+ * @return this
+ * @throws JSONException If the number is not finite.
+ */
+ public JSONWriter value(double d) throws JSONException {
+ return this.value(Double.valueOf(d));
+ }
+
+ /**
+ * Append a long value.
+ * @param l A long.
+ * @return this
+ * @throws JSONException
+ */
+ public JSONWriter value(long l) throws JSONException {
+ return this.append(Long.toString(l));
+ }
+
+
+ /**
+ * Append an object value.
+ * @param object The object to append. It can be null, or a Boolean, Number,
+ * String, JSONObject, or JSONArray, or an object that implements JSONString.
+ * @return this
+ * @throws JSONException If the value is out of sequence.
+ */
+ public JSONWriter value(Object object) throws JSONException {
+ return this.append(valueToString(object));
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/Property.java b/src/main/java/ch/m4th1eu/json/Property.java
new file mode 100644
index 0000000..14cefa8
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/Property.java
@@ -0,0 +1,75 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.util.Enumeration;
+import java.util.Properties;
+
+/**
+ * Converts a Property file data into JSONObject and back.
+ * @author JSON.org
+ * @version 2015-05-05
+ */
+public class Property {
+ /**
+ * Converts a property file object into a JSONObject. The property file object is a table of name value pairs.
+ * @param properties java.util.Properties
+ * @return JSONObject
+ * @throws JSONException
+ */
+ public static JSONObject toJSONObject(Properties properties) throws JSONException {
+ // can't use the new constructor for Android support
+ // JSONObject jo = new JSONObject(properties == null ? 0 : properties.size());
+ JSONObject jo = new JSONObject();
+ if (properties != null && !properties.isEmpty()) {
+ Enumeration> enumProperties = properties.propertyNames();
+ while(enumProperties.hasMoreElements()) {
+ String name = (String)enumProperties.nextElement();
+ jo.put(name, properties.getProperty(name));
+ }
+ }
+ return jo;
+ }
+
+ /**
+ * Converts the JSONObject into a property file object.
+ * @param jo JSONObject
+ * @return java.util.Properties
+ * @throws JSONException
+ */
+ public static Properties toProperties(JSONObject jo) throws JSONException {
+ Properties properties = new Properties();
+ if (jo != null) {
+ // Don't use the new entrySet API to maintain Android support
+ for (final String key : jo.keySet()) {
+ Object value = jo.opt(key);
+ if (!JSONObject.NULL.equals(value)) {
+ properties.put(key, value.toString());
+ }
+ }
+ }
+ return properties;
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/XML.java b/src/main/java/ch/m4th1eu/json/XML.java
new file mode 100644
index 0000000..2d293e2
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/XML.java
@@ -0,0 +1,767 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2015 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Iterator;
+
+/**
+ * This provides static methods to convert an XML text into a JSONObject, and to
+ * covert a JSONObject into an XML text.
+ *
+ * @author JSON.org
+ * @version 2016-08-10
+ */
+@SuppressWarnings("boxing")
+public class XML {
+
+ /** The Character '&'. */
+ public static final Character AMP = '&';
+
+ /** The Character '''. */
+ public static final Character APOS = '\'';
+
+ /** The Character '!'. */
+ public static final Character BANG = '!';
+
+ /** The Character '='. */
+ public static final Character EQ = '=';
+
+ /** The Character '>'. */
+ public static final Character GT = '>';
+
+ /** The Character '<'. */
+ public static final Character LT = '<';
+
+ /** The Character '?'. */
+ public static final Character QUEST = '?';
+
+ /** The Character '"'. */
+ public static final Character QUOT = '"';
+
+ /** The Character '/'. */
+ public static final Character SLASH = '/';
+
+ /**
+ * Null attrubute name
+ */
+ public static final String NULL_ATTR = "xsi:nil";
+
+ /**
+ * Creates an iterator for navigating Code Points in a string instead of
+ * characters. Once Java7 support is dropped, this can be replaced with
+ *
+ * string.codePoints()
+ *
+ * which is available in Java8 and above.
+ *
+ * @see http://stackoverflow.com/a/21791059/6030888
+ */
+ private static Iterable codePointIterator(final String string) {
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ private int nextIndex = 0;
+ private int length = string.length();
+
+ @Override
+ public boolean hasNext() {
+ return this.nextIndex < this.length;
+ }
+
+ @Override
+ public Integer next() {
+ int result = string.codePointAt(this.nextIndex);
+ this.nextIndex += Character.charCount(result);
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ /**
+ * Replace special characters with XML escapes:
+ *
+ *
+ * & (ampersand) is replaced by &
+ * < (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.
+ * @return true if the close tag is processed.
+ * @throws JSONException
+ */
+ private static boolean parse(XMLTokener x, JSONObject context, String name, XMLParserConfiguration config)
+ throws JSONException {
+ char c;
+ int i;
+ JSONObject jsonobject = null;
+ String string;
+ String tagName;
+ Object token;
+
+ // Test for and skip past these forms:
+ //
+ //
+ //
+ // ... ?>
+ // Report errors for these forms:
+ // <>
+ // <=
+ // <<
+
+ token = x.nextToken();
+
+ // ");
+ return false;
+ }
+ x.back();
+ } else if (c == '[') {
+ token = x.nextToken();
+ if ("CDATA".equals(token)) {
+ if (x.next() == '[') {
+ string = x.nextCDATA();
+ if (string.length() > 0) {
+ context.accumulate(config.cDataTagName, 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;
+ 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.convertNilAttributeToNull
+ && NULL_ATTR.equals(string)
+ && Boolean.parseBoolean((String) token)) {
+ nilAttributeFound = true;
+ } else if (!nilAttributeFound) {
+ jsonobject.accumulate(string,
+ config.keepStrings
+ ? ((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 (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) {
+ jsonobject.accumulate(config.cDataTagName,
+ config.keepStrings ? string : stringToValue(string));
+ }
+
+ } else if (token == LT) {
+ // Nested element
+ if (parse(x, jsonobject, tagName, config)) {
+ if (jsonobject.length() == 0) {
+ context.accumulate(tagName, "");
+ } else if (jsonobject.length() == 1
+ && jsonobject.opt(config.cDataTagName) != null) {
+ context.accumulate(tagName,
+ jsonobject.opt(config.cDataTagName));
+ } else {
+ context.accumulate(tagName, jsonobject);
+ }
+ return false;
+ }
+ }
+ }
+ } else {
+ throw x.syntaxError("Misshaped tag");
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is the same as {@link JSONObject#stringToValue(String)}.
+ *
+ * @param string String to convert
+ * @return JSON value of this string or the string
+ */
+ // To maintain compatibility with the Android API, this method is a direct copy of
+ // the one in JSONObject. Changes made here should be reflected there.
+ public static Object stringToValue(String string) {
+ if (string.equals("")) {
+ return string;
+ }
+ if (string.equalsIgnoreCase("true")) {
+ return Boolean.TRUE;
+ }
+ if (string.equalsIgnoreCase("false")) {
+ return Boolean.FALSE;
+ }
+ if (string.equalsIgnoreCase("null")) {
+ return JSONObject.NULL;
+ }
+
+ /*
+ * If it might be a number, try converting it. If a number cannot be
+ * produced, then the value will just be a string.
+ */
+
+ char initial = string.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ try {
+ // if we want full Big Number support this block can be replaced with:
+ // return stringToNumber(string);
+ if (string.indexOf('.') > -1 || string.indexOf('e') > -1
+ || string.indexOf('E') > -1 || "-0".equals(string)) {
+ Double d = Double.valueOf(string);
+ if (!d.isInfinite() && !d.isNaN()) {
+ return d;
+ }
+ } else {
+ Long myLong = Long.valueOf(string);
+ if (string.equals(myLong.toString())) {
+ if (myLong.longValue() == myLong.intValue()) {
+ return Integer.valueOf(myLong.intValue());
+ }
+ return myLong;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and <[ [ ]]>
+ * are ignored.
+ *
+ * @param string
+ * The source string.
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(String string) throws JSONException {
+ return toJSONObject(string, 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 <[ [ ]]>
+ * 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 <[ [ ]]>
+ * 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 <[ [ ]]>
+ * 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);
+ }
+ }
+ return jo;
+ }
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and <[ [ ]]>
+ * are ignored.
+ *
+ * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document.
+ *
+ * @param string
+ * The source string.
+ * @param keepStrings If true, then values will not be coerced into boolean
+ * or numeric values and will instead be left as strings
+ * @return A JSONObject containing the structured data from the XML string.
+ * @throws JSONException Thrown if there is an errors while parsing the string
+ */
+ public static JSONObject toJSONObject(String string, boolean keepStrings) throws JSONException {
+ return toJSONObject(new StringReader(string), keepStrings);
+ }
+
+ /**
+ * Convert a well-formed (but not necessarily valid) XML string into a
+ * JSONObject. Some information may be lost in this transformation because
+ * JSON is a data format and XML is a document format. XML uses elements,
+ * attributes, and content text, while JSON uses unordered collections of
+ * name/value pairs and arrays of values. JSON does not does not like to
+ * distinguish between elements and attributes. Sequences of similar
+ * elements are represented as JSONArrays. Content text may be placed in a
+ * "content" member. Comments, prologs, DTDs, and <[ [ ]]>
+ * are ignored.
+ *
+ * All values are converted as strings, for 1, 01, 29.0 will not be coerced to
+ * numbers but will instead be the exact value as seen in the XML document.
+ *
+ * @param string
+ * The source string.
+ * @param 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 {
+ StringBuilder sb = new StringBuilder();
+ JSONArray ja;
+ JSONObject jo;
+ String string;
+
+ if (object instanceof JSONObject) {
+
+ // Emit
+ if (tagName != null) {
+ sb.append('<');
+ sb.append(tagName);
+ sb.append('>');
+ }
+
+ // Loop thru the keys.
+ // don't use the new entrySet accessor to maintain Android Support
+ jo = (JSONObject) object;
+ for (final String key : jo.keySet()) {
+ Object value = jo.opt(key);
+ if (value == null) {
+ value = "";
+ } else if (value.getClass().isArray()) {
+ value = new JSONArray(value);
+ }
+
+ // Emit content in body
+ if (key.equals(config.cDataTagName)) {
+ 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));
+ sb.append("");
+ sb.append(key);
+ sb.append('>');
+ } else {
+ sb.append(toString(val, key, config));
+ }
+ }
+ } else if ("".equals(value)) {
+ sb.append('<');
+ sb.append(key);
+ sb.append("/>");
+
+ // Emit a new tag
+
+ } else {
+ sb.append(toString(value, key, config));
+ }
+ }
+ if (tagName != null) {
+
+ // Emit the close tag
+ sb.append("");
+ sb.append(tagName);
+ sb.append('>');
+ }
+ return sb.toString();
+
+ }
+
+ if (object != null && (object instanceof JSONArray || object.getClass().isArray())) {
+ if(object.getClass().isArray()) {
+ ja = new JSONArray(object);
+ } else {
+ ja = (JSONArray) object;
+ }
+ int jaLength = ja.length();
+ // don't use the new iterator API to maintain support for Android
+ for (int i = 0; i < jaLength; i++) {
+ Object val = ja.opt(i);
+ // XML does not have good support for arrays. If an array
+ // appears in a place where XML is lacking, synthesize an
+ // element.
+ sb.append(toString(val, tagName == null ? "array" : tagName, config));
+ }
+ return sb.toString();
+ }
+
+ string = (object == null) ? "null" : escape(object.toString());
+ return (tagName == null) ? "\"" + string + "\""
+ : (string.length() == 0) ? "<" + tagName + "/>" : "<" + tagName
+ + ">" + string + "" + tagName + ">";
+
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/XMLParserConfiguration.java b/src/main/java/ch/m4th1eu/json/XMLParserConfiguration.java
new file mode 100644
index 0000000..d3f2040
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/XMLParserConfiguration.java
@@ -0,0 +1,107 @@
+package ch.m4th1eu.json;
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+/**
+ * Configuration object for the XML parser.
+ * @author AylwardJ
+ *
+ */
+public class XMLParserConfiguration {
+ /** 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(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)
+ */
+ public final 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.
+ */
+ public final 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)
+ */
+ public final boolean convertNilAttributeToNull;
+
+ /**
+ * Default parser configuration. Does not keep strings, and the CDATA Tag Name is "content".
+ */
+ public XMLParserConfiguration () {
+ this(false, "content", false);
+ }
+
+ /**
+ * 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.
+ */
+ 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 cDataTagNamenull
to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ */
+ 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 cDataTagNamenull
to disable CDATA processing. Any other value
+ * to use that value as the JSONObject key name to process as CDATA.
+ */
+ 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}.
+ */
+ public XMLParserConfiguration (final boolean keepStrings, final String cDataTagName, final boolean convertNilAttributeToNull) {
+ this.keepStrings = keepStrings;
+ this.cDataTagName = cDataTagName;
+ this.convertNilAttributeToNull = convertNilAttributeToNull;
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/json/XMLTokener.java b/src/main/java/ch/m4th1eu/json/XMLTokener.java
new file mode 100644
index 0000000..9bd3143
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/json/XMLTokener.java
@@ -0,0 +1,407 @@
+package ch.m4th1eu.json;
+
+/*
+Copyright (c) 2002 JSON.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+import java.io.Reader;
+
+/**
+ * The XMLTokener extends the JSONTokener to provide additional methods
+ * for the parsing of XML texts.
+ * @author JSON.org
+ * @version 2015-12-09
+ */
+public class XMLTokener extends JSONTokener {
+
+
+ /** The table of entity values. It initially contains Character values for
+ * amp, apos, gt, lt, quot.
+ */
+ public static final java.util.HashMap entity;
+
+ static {
+ entity = new java.util.HashMap(8);
+ entity.put("amp", XML.AMP);
+ entity.put("apos", XML.APOS);
+ entity.put("gt", XML.GT);
+ entity.put("lt", XML.LT);
+ entity.put("quot", XML.QUOT);
+ }
+
+ /**
+ * Construct an XMLTokener from a Reader.
+ * @param r A source reader.
+ */
+ public XMLTokener(Reader r) {
+ super(r);
+ }
+
+ /**
+ * Construct an XMLTokener from a string.
+ * @param s A source string.
+ */
+ public XMLTokener(String s) {
+ super(s);
+ }
+
+ /**
+ * Get the text in the CDATA block.
+ * @return The string up to the ]]>
.
+ * @throws JSONException If the ]]>
is not found.
+ */
+ public String nextCDATA() throws JSONException {
+ char c;
+ int i;
+ StringBuilder sb = new StringBuilder();
+ while (more()) {
+ c = next();
+ sb.append(c);
+ i = sb.length() - 3;
+ if (i >= 0 && sb.charAt(i) == ']' &&
+ sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') {
+ sb.setLength(i);
+ return sb.toString();
+ }
+ }
+ throw syntaxError("Unclosed CDATA");
+ }
+
+
+ /**
+ * Get the next XML outer token, trimming whitespace. There are two kinds
+ * of tokens: the '<' character which begins a markup tag, and the content
+ * text between markup tags.
+ *
+ * @return A string, or a '<' Character, or null if there is no more
+ * source text.
+ * @throws JSONException
+ */
+ public Object nextContent() throws JSONException {
+ char c;
+ StringBuilder sb;
+ do {
+ c = next();
+ } while (Character.isWhitespace(c));
+ if (c == 0) {
+ return null;
+ }
+ if (c == '<') {
+ return XML.LT;
+ }
+ sb = new StringBuilder();
+ for (;;) {
+ if (c == 0) {
+ return sb.toString().trim();
+ }
+ if (c == '<') {
+ back();
+ return sb.toString().trim();
+ }
+ if (c == '&') {
+ sb.append(nextEntity(c));
+ } else {
+ sb.append(c);
+ }
+ c = next();
+ }
+ }
+
+
+ /**
+ * Return the next entity. These entities are translated to Characters:
+ * & ' > < "
.
+ * @param ampersand An ampersand character.
+ * @return A Character or an entity String if the entity is not recognized.
+ * @throws JSONException If missing ';' in XML entity.
+ */
+ public Object nextEntity(@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);
+ }
+
+ /**
+ * Unescapes an XML entity encoding;
+ * @param e entity (only the actual entity value, not the preceding & or ending ;
+ * @return
+ */
+ static String unescapeEntity(String e) {
+ // validate
+ if (e == null || e.isEmpty()) {
+ return "";
+ }
+ // if our entity is an encoded unicode point, parse it.
+ if (e.charAt(0) == '#') {
+ int cp;
+ if (e.charAt(1) == 'x') {
+ // hex encoded unicode
+ cp = Integer.parseInt(e.substring(2), 16);
+ } else {
+ // decimal encoded unicode
+ cp = Integer.parseInt(e.substring(1));
+ }
+ return new String(new int[] {cp},0,1);
+ }
+ Character knownEntity = entity.get(e);
+ if(knownEntity==null) {
+ // we don't know the entity so keep it encoded
+ return '&' + e + ';';
+ }
+ return knownEntity.toString();
+ }
+
+
+ /**
+ * Returns the next XML meta token. This is used for skipping over
+ * and ...?> structures.
+ * @return Syntax characters (< > / = ! ?
) are returned as
+ * Character, and strings and names are returned as Boolean. We don't care
+ * what the values actually are.
+ * @throws JSONException If a string is not properly closed or if the XML
+ * is badly structured.
+ */
+ public Object nextMeta() throws JSONException {
+ char c;
+ char q;
+ do {
+ c = next();
+ } while (Character.isWhitespace(c));
+ switch (c) {
+ case 0:
+ throw syntaxError("Misshaped meta tag");
+ case '<':
+ return XML.LT;
+ case '>':
+ return XML.GT;
+ case '/':
+ return XML.SLASH;
+ case '=':
+ return XML.EQ;
+ case '!':
+ return XML.BANG;
+ case '?':
+ return XML.QUEST;
+ case '"':
+ case '\'':
+ q = c;
+ for (;;) {
+ c = next();
+ if (c == 0) {
+ throw syntaxError("Unterminated string");
+ }
+ if (c == q) {
+ return Boolean.TRUE;
+ }
+ }
+ default:
+ for (;;) {
+ c = next();
+ if (Character.isWhitespace(c)) {
+ return Boolean.TRUE;
+ }
+ switch (c) {
+ case 0:
+ case '<':
+ case '>':
+ case '/':
+ case '=':
+ case '!':
+ case '?':
+ case '"':
+ case '\'':
+ back();
+ return Boolean.TRUE;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Get the next XML Token. These tokens are found inside of angle
+ * brackets. It may be one of these characters: / > = ! ?
or it
+ * may be a string wrapped in single quotes or double quotes, or it may be a
+ * name.
+ * @return a String or a Character.
+ * @throws JSONException If the XML is not well formed.
+ */
+ public Object nextToken() throws JSONException {
+ char c;
+ char q;
+ StringBuilder sb;
+ do {
+ c = next();
+ } while (Character.isWhitespace(c));
+ switch (c) {
+ case 0:
+ throw syntaxError("Misshaped element");
+ case '<':
+ throw syntaxError("Misplaced '<'");
+ case '>':
+ return XML.GT;
+ case '/':
+ return XML.SLASH;
+ case '=':
+ return XML.EQ;
+ case '!':
+ return XML.BANG;
+ case '?':
+ return XML.QUEST;
+
+// Quoted string
+
+ case '"':
+ case '\'':
+ q = c;
+ sb = new StringBuilder();
+ for (;;) {
+ c = next();
+ if (c == 0) {
+ throw syntaxError("Unterminated string");
+ }
+ if (c == q) {
+ return sb.toString();
+ }
+ if (c == '&') {
+ sb.append(nextEntity(c));
+ } else {
+ sb.append(c);
+ }
+ }
+ default:
+
+// Name
+
+ sb = new StringBuilder();
+ for (;;) {
+ sb.append(c);
+ c = next();
+ if (Character.isWhitespace(c)) {
+ return sb.toString();
+ }
+ switch (c) {
+ case 0:
+ return sb.toString();
+ case '>':
+ case '/':
+ case '=':
+ case '!':
+ case '?':
+ case '[':
+ case ']':
+ back();
+ return sb.toString();
+ case '<':
+ case '"':
+ case '\'':
+ throw syntaxError("Bad character in a name");
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Skip characters until past the requested string.
+ * If it is not found, we are left at the end of the source with a result of false.
+ * @param to A string to skip past.
+ */
+ // The Android implementation of JSONTokener has a public method of public void skipPast(String to)
+ // even though ours does not have that method, to have API compatibility, our method in the subclass
+ // should match.
+ public void skipPast(String to) {
+ boolean b;
+ char c;
+ int i;
+ int j;
+ int offset = 0;
+ int length = to.length();
+ char[] circle = new char[length];
+
+ /*
+ * First fill the circle buffer with as many characters as are in the
+ * to string. If we reach an early end, bail.
+ */
+
+ for (i = 0; i < length; i += 1) {
+ c = next();
+ if (c == 0) {
+ return;
+ }
+ circle[i] = c;
+ }
+
+ /* We will loop, possibly for all of the remaining characters. */
+
+ for (;;) {
+ j = offset;
+ b = true;
+
+ /* Compare the circle buffer with the to string. */
+
+ for (i = 0; i < length; i += 1) {
+ if (circle[j] != to.charAt(i)) {
+ b = false;
+ break;
+ }
+ j += 1;
+ if (j >= length) {
+ j -= length;
+ }
+ }
+
+ /* If we exit the loop with b intact, then victory is ours. */
+
+ if (b) {
+ return;
+ }
+
+ /* Get the next character. If there isn't one, then defeat is ours. */
+
+ c = next();
+ if (c == 0) {
+ return;
+ }
+ /*
+ * Shove the character in the circle buffer and advance the
+ * circle offset. The offset is mod n.
+ */
+ circle[offset] = c;
+ offset += 1;
+ if (offset >= length) {
+ offset -= length;
+ }
+ }
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/richpresence/Main.java b/src/main/java/ch/m4th1eu/richpresence/Main.java
index eb38b6e..0437311 100644
--- a/src/main/java/ch/m4th1eu/richpresence/Main.java
+++ b/src/main/java/ch/m4th1eu/richpresence/Main.java
@@ -1,10 +1,11 @@
package ch.m4th1eu.richpresence;
+import ch.m4th1eu.json.JSONArray;
+import ch.m4th1eu.json.JSONObject;
import ch.m4th1eu.richpresence.events.AdvancedStatusEvent;
import ch.m4th1eu.richpresence.events.EventPresence;
import ch.m4th1eu.richpresence.proxy.CommonProxy;
import net.minecraftforge.common.MinecraftForge;
-import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.Mod.EventHandler;
import net.minecraftforge.fml.common.SidedProxy;
@@ -12,6 +13,8 @@ import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import org.apache.logging.log4j.Logger;
+import java.io.*;
+
/**
* @author M4TH1EU_#0001
*/
@@ -19,14 +22,14 @@ import org.apache.logging.log4j.Logger;
public class Main {
public static final String MODID = "richpresence";
public static final String NAME = "Discord Rich Presence";
- public static final String VERSION = "1.1";
+ public static final String VERSION = "1.2";
@SidedProxy(clientSide = "ch.m4th1eu.richpresence.proxy.ClientProxy", serverSide = "ch.m4th1eu.richpresence.proxy.CommonProxy")
public static CommonProxy proxy;
/**
* Variables pour la config
*/
- public static String applicationId, largeimage, largeimagetext;
+ public static String applicationId, largeimage, largeimagetext, serveurIP;
public static boolean advancedstatus;
@@ -35,7 +38,9 @@ public class Main {
public Main() {
- MinecraftForge.EVENT_BUS.register(new AdvancedStatusEvent());
+ if (advancedstatus) {
+ MinecraftForge.EVENT_BUS.register(new AdvancedStatusEvent());
+ }
}
@EventHandler
@@ -44,24 +49,62 @@ public class Main {
proxy.preInit(event.getSuggestedConfigurationFile());
//Configuration
- Configuration cfg = new Configuration(event.getSuggestedConfigurationFile());
- try {
- cfg.load();
- applicationId = cfg.getString("applicationID", "DiscordRichPresence", "ex: 491941559181246465", "Ici mettez le client id de votre application (https://bit.ly/2Lu1CC3).");
- largeimage = cfg.getString("largeimage", "DiscordRichPresence", "logo", "Ici mettez le nom de votre image (celle dans RichPresence -> Art Assets). (sans le .png)");
- largeimagetext = cfg.getString("largeimagetext", "DiscordRichPresence", "Mon serveur !", "Ici mettez le texte qui s'affichera quand vous passerez la souris sur l'image.");
- advancedstatus = cfg.getBoolean("advancedstatus", "DiscordRichPresence", true, "Ici laissez \"true\" si vous voulez un status avancé sinon mettez \"false\".");
+ event.getModConfigurationDirectory().mkdir();
+ File config_file = new File(event.getModConfigurationDirectory(), "\\" + Main.MODID + ".json");
+ if (!config_file.exists()) {
+ try {
+ event.getModLog().warn("Impossible de charger la configuration du mod : " + Main.MODID);
+ event.getModLog().warn("Création du fichier de configuration");
+ config_file.createNewFile();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
- } catch (Exception ex) {
- event.getModLog().error("Failed to load configuration");
- } finally {
- if (cfg.hasChanged()) {
- cfg.save();
+ if (config_file.length() < 10) {
+ try {
+ PrintWriter writer = new PrintWriter(config_file, "UTF-8");
+ writer.println("{\n" +
+ " \"application-settings\": [\n" +
+ " {\n" +
+ " \"applicationID\": \"601875975533232158\",\n" +
+ " \"large-image-name\": \"discord_logo\",\n" +
+ " \"large-image-text\": \"En train de tester ce mod !\"\n" +
+ " }\n" +
+ " ],\n" +
+ " \"advanced-status\": true,\n" +
+ " \"advanced-status-custom\": [\n" +
+ " {\n" +
+ " \"onJoinServer\": {\n" +
+ " \"message\": \"En jeu.\"\n" +
+ " },\n" +
+ " \"onQuitServer\": {\n" +
+ " \"message\": \"Dans le menu principal.\"\n" +
+ " },\n" +
+ " \"inPauseMenu\": {\n" +
+ " \"message\": \"Dans le menu pause\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ "}\n" +
+ "\n");
+ writer.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
}
}
+ JSONObject config = new JSONObject(Utils.readFileToString(config_file));
+
+ applicationId = config.getJSONObject("application-settings").getString("applicationID");
+ largeimage = config.getJSONObject("application-settings").getString("large-image-name");
+ largeimagetext = config.getJSONObject("application-settings").getString("large-image-text");
+
+ advancedstatus = config.getBoolean("advanced-status");
}
@EventHandler
@@ -71,7 +114,7 @@ public class Main {
rpcClient = new EventPresence();
proxy.rpcinit();
- if (Main.advancedstatus) {
+ if (advancedstatus) {
proxy.rpcupdate("Dans le menu.", null);
} else {
proxy.rpcupdate("", null);
diff --git a/src/main/java/ch/m4th1eu/richpresence/Utils.java b/src/main/java/ch/m4th1eu/richpresence/Utils.java
new file mode 100644
index 0000000..f853088
--- /dev/null
+++ b/src/main/java/ch/m4th1eu/richpresence/Utils.java
@@ -0,0 +1,30 @@
+package ch.m4th1eu.richpresence;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+
+public class Utils {
+
+ /**
+ * @author Nathanaël#4314
+ */
+ public static String readFileToString(final File file) {
+ try {
+ final BufferedReader reader = new BufferedReader(new FileReader(file));
+ final StringBuilder stringBuilder = new StringBuilder();
+ String line;
+ final String ls = System.getProperty("line.separator");
+ while ((line = reader.readLine()) != null) {
+ stringBuilder.append(line);
+ stringBuilder.append(ls);
+ }
+ stringBuilder.deleteCharAt(stringBuilder.length() - 1);
+ reader.close();
+ return stringBuilder.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "ERROR";
+ }
+ }
+}
diff --git a/src/main/java/ch/m4th1eu/richpresence/events/AdvancedStatusEvent.java b/src/main/java/ch/m4th1eu/richpresence/events/AdvancedStatusEvent.java
index f329432..f77fb2a 100644
--- a/src/main/java/ch/m4th1eu/richpresence/events/AdvancedStatusEvent.java
+++ b/src/main/java/ch/m4th1eu/richpresence/events/AdvancedStatusEvent.java
@@ -1,22 +1,32 @@
package ch.m4th1eu.richpresence.events;
+import ch.m4th1eu.json.JSONObject;
import ch.m4th1eu.richpresence.Main;
+import ch.m4th1eu.richpresence.Utils;
+import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiIngameMenu;
+import net.minecraft.server.MinecraftServer;
import net.minecraftforge.client.event.GuiOpenEvent;
+import net.minecraftforge.fml.client.FMLClientHandler;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.common.network.FMLNetworkEvent;
+import java.io.File;
+
/**
* @author M4TH1EU_#0001
*/
@Mod.EventBusSubscriber
public class AdvancedStatusEvent {
+ File config_file = new File(Minecraft.getMinecraft().mcDataDir, "\\config\\" + Main.MODID + ".json");
+ JSONObject config = new JSONObject(Utils.readFileToString(config_file));
+
@SubscribeEvent
public void onServerJoin(FMLNetworkEvent.ClientConnectedToServerEvent event) {
if (Main.advancedstatus) {
- Main.proxy.rpcupdate("En jeu.", null);
+ Main.proxy.rpcupdate(config.getJSONArray("advanced-status-custom").getJSONObject(0).getString("message"), null);
}
}
@@ -24,7 +34,7 @@ public class AdvancedStatusEvent {
public void onQuitServer(FMLNetworkEvent.ClientDisconnectionFromServerEvent event) {
if (Main.advancedstatus) {
- Main.proxy.rpcupdate("Dans le menu principal.", null);
+ Main.proxy.rpcupdate(config.getJSONArray("advanced-status-custom").getJSONObject(1).getString("message"), null);
}
}
@@ -32,12 +42,9 @@ public class AdvancedStatusEvent {
public void onGuiOpen(GuiOpenEvent event) {
if (Main.advancedstatus) {
if (event.getGui() instanceof GuiIngameMenu) {
- Main.proxy.rpcupdate("Dans le menu pause.", null);
+ Main.proxy.rpcupdate(config.getJSONArray("advanced-status-custom").getJSONObject(2).getString("message"), null);
}
-
- System.out.println(event.getGui());
}
-
}