All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javaxt.json.JSONObject Maven / Gradle / Ivy

package javaxt.json;

import java.io.IOException;
import java.io.Writer;
import java.util.Map.Entry;

import javaxt.utils.Value;

//******************************************************************************
//**  JSONObject
//******************************************************************************
/**
 * Used to create and parse JSON documents. JSON documents are an unordered
 * collection of name/value pairs. Its external form is a string wrapped in
 * curly braces with colons between the names and values, and commas between the
 * values and names.
 *
 * @author json.org
 * @version 2016-08-15
 *
 ******************************************************************************/

public class JSONObject {

	private final java.util.LinkedHashMap map;

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	public JSONObject() {
		map = new java.util.LinkedHashMap();
	}

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	/**
	 * Construct a JSONObject from a source JSON text string. This is the most
	 * commonly used JSONObject constructor.
	 *
	 * @param source
	 *            A string beginning with { (left
	 *            brace) and ending with }  
	 *            (right brace) .
	 */
	public JSONObject(String source) throws JSONException {
		this(new JSONTokener(source));
	}

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	/**
	 * Construct a JSONObject from a JSONTokener.
	 */
	protected JSONObject(JSONTokener x) throws JSONException {
		this();
		char c;
		String key;

		if (x.nextClean() != '{') {
			throw x.syntaxError("A JSONObject text must begin with '{'");
		}
		for (;;) {
			c = x.nextClean();
			switch (c) {
			case 0:
				throw x.syntaxError("A JSONObject text must end with '}'");
			case '}':
				return;
			default:
				x.back();
				key = x.nextValue().toString();
			}

			// The key is followed by ':'.

			c = x.nextClean();
			if (c != ':') {
				throw x.syntaxError("Expected a ':' after a key");
			}

			// Use syntaxError(..) to include error location

			if (key != null) {
				// Check if key exists
				if (map.get(key) != null) {
					// key already exists
					throw x.syntaxError("Duplicate key \"" + key + "\"");
				}
				// Only add value if non-null
				Object value = x.nextValue();
				if (value != null) {
					put(key, value);
				}
			}

			// Pairs are separated by ','.

			switch (x.nextClean()) {
			case ';':
			case ',':
				if (x.nextClean() == '}') {
					return;
				}
				x.back();
				break;
			case '}':
				return;
			default:
				throw x.syntaxError("Expected a ',' or '}'");
			}
		}
	}

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	/**
	 * Construct a JSONObject from an XML Document.
	 */
	public JSONObject(org.w3c.dom.Document xml) throws JSONException {
		this(javaxt.xml.DOM.getOuterNode(xml));
	}

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	/**
	 * Construct a JSONObject from an XML Node.
	 */
	public JSONObject(org.w3c.dom.Node node) throws JSONException {
		JSONObject json = new JSONObject();
		if (javaxt.xml.DOM.hasChildren(node)) {
			traverse(node, json);
		} else {
			json.setValue(node.getNodeName(), node.getTextContent());
		}

		this.map = json.map;
	}

	private void traverse(org.w3c.dom.Node node, JSONObject json) {
		if (node.getNodeType() == 1) {
			if (javaxt.xml.DOM.hasChildren(node)) {
				JSONObject _json = new JSONObject();
				org.w3c.dom.NodeList xmlNodeList = node.getChildNodes();
				for (int i = 0; i < xmlNodeList.getLength(); i++) {
					traverse(xmlNodeList.item(i), _json);
				}
				json.setValue(node.getNodeName(), _json);
			} else {
				json.setValue(node.getNodeName(), node.getTextContent());
			}
		}
	}

	// **************************************************************************
	// ** getValue
	// **************************************************************************
	/**
	 * Returns the value associated with a key.
	 */
	public Value getValue(String key) {
		if (key == null)
			return new Value(null);
		return new Value(map.get(key));
	}

	// **************************************************************************
	// ** getJSONArray
	// **************************************************************************
	/**
	 * Returns the JSONArray associated with a key.
	 */
	public JSONArray getJSONArray(String key) {
		Object object = map.get(key);
		if (object instanceof JSONArray) {
			return (JSONArray) object;
		}
		return null;
	}

	// **************************************************************************
	// ** getJSONObject
	// **************************************************************************
	/**
	 * Returns the JSONObject associated with a key.
	 */
	public JSONObject getJSONObject(String key) {
		Object object = map.get(key);
		if (object instanceof JSONObject) {
			return (JSONObject) object;
		}
		return null;
	}

	// **************************************************************************
	// ** has
	// **************************************************************************
	/**
	 * Returns true if the key exists in the JSONObject.
	 */
	public boolean has(String key) {
		return this.map.containsKey(key);
	}

	// **************************************************************************
	// ** isNull
	// **************************************************************************
	/**
	 * Returns true if there is no value associated with the key.
	 */
	public boolean isNull(String key) {
		return map.get(key) == null;
	}

	// **************************************************************************
	// ** keys
	// **************************************************************************
	/**
	 * Returns an enumeration of the keys of the JSONObject. Modifying this key
	 * Set will also modify the JSONObject. Use with caution.
	 */
	public java.util.Iterator keys() {
		return this.keySet().iterator();
	}

	// **************************************************************************
	// ** keySet
	// **************************************************************************
	/**
	 * Returns a set of keys of the JSONObject. Modifying this key Set will also
	 * modify the JSONObject. Use with caution.
	 */
	public java.util.Set keySet() {
		return this.map.keySet();
	}

	// **************************************************************************
	// ** entrySet
	// **************************************************************************
	private java.util.Set> entrySet() {
		return this.map.entrySet();
	}

	// **************************************************************************
	// ** length
	// **************************************************************************
	/**
	 * Returns the number of keys in the JSONObject.
	 */
	public int length() {
		return this.map.size();
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a boolean.
	 */
	public void setValue(String key, Boolean value) throws JSONException {
		put(key, value);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a double.
	 */
	public void setValue(String key, Double value) throws JSONException {
		put(key, value);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a float.
	 */
	public void setValue(String key, Float value) throws JSONException {
		put(key, value);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with an integer.
	 */
	public void setValue(String key, Integer value) throws JSONException {
		put(key, value);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a long.
	 */
	public void setValue(String key, Long value) throws JSONException {
		put(key, value);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a String.
	 */
	public void setValue(String key, String str) throws JSONException {
		if (str != null) {
			str = str.trim();
			if (str.length() == 0)
				str = null;
		}
		put(key, str);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a JSONObject.
	 */
	public void setValue(String key, JSONObject json) throws JSONException {
		put(key, json);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a JSONArray.
	 */
	public void setValue(String key, JSONArray arr) throws JSONException {
		put(key, arr);
	}

	// **************************************************************************
	// ** setValue
	// **************************************************************************
	/**
	 * Used to set the value for a given key with a javaxt.utils.Value.
	 */
	public void setValue(String key, Value val) throws JSONException {
		Object obj = null;
		if (val != null)
			obj = val.toObject();
		put(key, obj);
	}

	// **************************************************************************
	// ** put
	// **************************************************************************
	/**
	 * Put a key/value pair in the JSONObject. If the value is null, then the
	 * key will be removed from the JSONObject if it is present.
	 * 
	 * @param key
	 *            A key string.
	 * @param value
	 *            An object which is the value. It should be of one of these
	 *            types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
	 *            or String.
	 */
	private void put(String key, Object value) throws JSONException {
		if (key == null) {
			throw new NullPointerException("Null key.");
		}
		if (value != null) {
			testValidity(value);
			map.put(key, value);
		} else {
			map.remove(key);
		}
	}

	// **************************************************************************
	// ** remove
	// **************************************************************************
	/**
	 * Remove a name and its value, if present. Returns the value that was
	 * associated with the name, or null if there was no value.
	 */
	public Object remove(String key) {
		return map.remove(key);
	}

	// **************************************************************************
	// ** equals
	// **************************************************************************
	/**
	 * Returns true if the given object is a JSONObject and the JSONObject
	 * contains the same key/value pairs. Order is not important.
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj instanceof JSONObject) {
			JSONObject json = (JSONObject) obj;
			if (json.length() == this.length()) {
				java.util.Iterator it = map.keySet().iterator();
				while (it.hasNext()) {
					String key = it.next();
					if (!json.has(key))
						return false;
					Object val = map.get(key);
					Object val2 = json.getValue(key).toObject();
					if (val == null) {
						if (val2 != null)
							return false;
					} else {
						if (!val.equals(val2))
							return false;
					}
				}
				return true;
			}
		}
		return false;
	}

	// **************************************************************************
	// ** toString
	// **************************************************************************
	/**
	 * Returns the JSONObject as a String. For compactness, no whitespace is
	 * added. If this would not result in a syntactically correct JSON text,
	 * then null will be returned instead.
	 */
	@Override
	public String toString() {
		try {
			return this.toString(0);
		} catch (Exception e) {
			return null;
		}
	}

	// **************************************************************************
	// ** toString
	// **************************************************************************
	/**
	 * Returns a pretty-printed JSON text of this JSONObject.
	 * 
	 * @param indentFactor
	 *            The number of spaces to add to each level of indentation.
	 */
	public String toString(int indentFactor) {
		try {
			java.io.StringWriter w = new java.io.StringWriter();
			synchronized (w.getBuffer()) {
				return this.write(w, indentFactor, 0).toString();
			}
		} catch (Exception e) {
			return null;
		}
	}

	// **************************************************************************
	// ** writeValue
	// **************************************************************************
	protected 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 Number) {
			// not all Numbers may match actual JSON Numbers. i.e. fractions or
			// Imaginary
			final String numberAsString = numberToString((Number) value);
			try {
				// Use the BigDecimal constructor for its parser to validate the
				// format.
				@SuppressWarnings("unused")
				java.math.BigDecimal testNum = new java.math.BigDecimal(numberAsString);
				// Close enough to a JSON number that we will use it unquoted
				writer.write(numberAsString);
			} catch (NumberFormatException ex) {
				// 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;
	}

	// **************************************************************************
	// ** write
	// **************************************************************************
	private 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);
		}
	}

	// **************************************************************************
	// ** indent
	// **************************************************************************
	protected static final void indent(Writer writer, int indent) throws IOException {
		for (int i = 0; i < indent; i += 1) {
			writer.write(' ');
		}
	}

	// **************************************************************************
	// ** testValidity
	// **************************************************************************
	protected 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.");
				}
			}
		}
	}

	// **************************************************************************
	// ** numberToString
	// **************************************************************************
	/**
	 * Produce a string from a Number.
	 */
	private static String numberToString(Number number) throws JSONException {
		if (number == null) {
			throw new JSONException("Null pointer");
		}
		testValidity(number);

		// Shave off trailing zeros and decimal point, if possible.

		String string = number.toString();
		if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) {
			while (string.endsWith("0")) {
				string = string.substring(0, string.length() - 1);
			}
			if (string.endsWith(".")) {
				string = string.substring(0, string.length() - 1);
			}
		}
		return string;
	}

	// **************************************************************************
	// ** quote
	// **************************************************************************
	/**
	 * Returns a String correctly formatted for insertion in a JSON text.
	 */
	private static String quote(String string) {
		java.io.StringWriter sw = new java.io.StringWriter();
		synchronized (sw.getBuffer()) {
			try {
				return quote(string, sw).toString();
			} catch (IOException ignored) {
				// will never happen - we are writing to a string writer
				return "";
			}
		}
	}

	private static Writer quote(String string, Writer w) throws IOException {
		if (string == null || string.length() == 0) {
			w.write("\"\"");
			return w;
		}

		char b;
		char c = 0;
		String hhhh;
		int i;
		int len = string.length();

		w.write('"');
		for (i = 0; i < len; i += 1) {
			b = c;
			c = string.charAt(i);
			switch (c) {
			case '\\':
			case '"':
				w.write('\\');
				w.write(c);
				break;
			case '/':
				if (b == '<') {
					w.write('\\');
				}
				w.write(c);
				break;
			case '\b':
				w.write("\\b");
				break;
			case '\t':
				w.write("\\t");
				break;
			case '\n':
				w.write("\\n");
				break;
			case '\f':
				w.write("\\f");
				break;
			case '\r':
				w.write("\\r");
				break;
			default:
				if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
					w.write("\\u");
					hhhh = Integer.toHexString(c);
					w.write("0000", 0, 4 - hhhh.length());
					w.write(hhhh);
				} else {
					w.write(c);
				}
			}
		}
		w.write('"');
		return w;
	}
}

// ******************************************************************************
// ** JSONTokener
// ******************************************************************************
/**
 * 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
 *
 ******************************************************************************/

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 java.io.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;

	// **************************************************************************
	// ** Constructor
	// **************************************************************************
	protected JSONTokener(String s) {
		java.io.Reader reader = new java.io.StringReader(s);
		this.reader = reader.markSupported() ? reader : new java.io.BufferedReader(reader);
		this.eof = false;
		this.usePrevious = false;
		this.previous = 0;
		this.index = 0;
		this.character = 1;
		this.characterPreviousLine = 0;
		this.line = 1;
	}

	/**
	 * 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
	 */
	protected 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.
	 */
	private 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
	 */
	private 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.
	 */
	private 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.
	 */
	private 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.
	 */
	private 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.
	 */
	private 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.
	 */
	protected 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.
	 */
	private 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
	 */
	private 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
	 */
	private 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.
	 * 
	 * @throws JSONException
	 *             If syntax error.
	 */
	protected 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();
		}
		this.back();

		string = sb.toString().trim();
		if ("".equals(string)) {
			throw this.syntaxError("Missing value");
		}
		return 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
	 */
	private 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
	 */
	protected 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
	 */
	private 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 + "]";
	}

	// **************************************************************************
	// ** stringToValue
	// **************************************************************************
	/**
	 * Try to convert a string into a number, boolean, or null. If the string
	 * can't be converted, return the string.
	 */
	private 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 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 (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;
	}

	// **************************************************************************
	// ** isDecimalNotation
	// **************************************************************************
	/**
	 * Tests if the value should be treated as a decimal. It makes no test if
	 * there are actual digits.
	 *
	 * @return true if the string is "-0" or if it contains '.', 'e', or 'E',
	 *         false otherwise.
	 */
	private static boolean isDecimalNotation(final String val) {
		return val.indexOf('.') > -1 || val.indexOf('e') > -1 || val.indexOf('E') > -1 || "-0".equals(val);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy