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

org.springframework.boot.configurationprocessor.json.JSONTokener Maven / Gradle / Ivy

There is a newer version: 3.3.0
Show newest version
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.configurationprocessor.json;

// Note: this class was written without inspecting the non-free org.json source code.

/**
 * Parses a JSON (RFC 4627) encoded
 * string into the corresponding object. Most clients of this class will use only need the
 * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage:
 * 
 * String json = "{"
 *         + "  \"query\": \"Pizza\", "
 *         + "  \"locations\": [ 94043, 90210 ] "
 *         + "}";
 *
 * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
 * String query = object.getString("query");
 * JSONArray locations = object.getJSONArray("locations");
*

* For best interoperability and performance use JSON that complies with RFC 4627, such as * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a * successful parse does not indicate that the input string was valid JSON. All of the * following syntax errors will be ignored: *

    *
  • End of line comments starting with {@code //} or {@code #} and ending with a * newline character. *
  • C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such * comments may not be nested. *
  • Strings that are unquoted or {@code 'single quoted'}. *
  • Hexadecimal integers prefixed with {@code 0x} or {@code 0X}. *
  • Octal integers prefixed with {@code 0}. *
  • Array elements separated by {@code ;}. *
  • Unnecessary array separators. These are interpreted as if null was the omitted * value. *
  • Key-value pairs separated by {@code =} or {@code =>}. *
  • Key-value pairs separated by {@code ;}. *
*

* Each tokener may be used to parse a single JSON string. Instances of this class are not * thread safe. Although this class is nonfinal, it was not designed for inheritance and * should not be subclassed. In particular, self-use by overrideable methods is not * specified. See Effective Java Item 17, "Design and Document or inheritance or * else prohibit it" for further information. */ public class JSONTokener { /** * The input JSON. */ private final String in; /** * The index of the next character to be returned by {@link #next}. When the input is * exhausted, this equals the input's length. */ private int pos; /** * @param in JSON encoded string. Null is not permitted and will yield a tokener that * throws {@code NullPointerExceptions} when methods are called. */ public JSONTokener(String in) { // consume an optional byte order mark (BOM) if it exists if (in != null && in.startsWith("\ufeff")) { in = in.substring(1); } this.in = in; } /** * Returns the next value from the input. * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long, * Double or {@link JSONObject#NULL}. * @throws JSONException if the input is malformed. */ public Object nextValue() throws JSONException { int c = nextCleanInternal(); switch (c) { case -1: throw syntaxError("End of input"); case '{': return readObject(); case '[': return readArray(); case '\'': case '"': return nextString((char) c); default: this.pos--; return readLiteral(); } } private int nextCleanInternal() throws JSONException { while (this.pos < this.in.length()) { int c = this.in.charAt(this.pos++); switch (c) { case '\t': case ' ': case '\n': case '\r': continue; case '/': if (this.pos == this.in.length()) { return c; } char peek = this.in.charAt(this.pos); switch (peek) { case '*': // skip a /* c-style comment */ this.pos++; int commentEnd = this.in.indexOf("*/", this.pos); if (commentEnd == -1) { throw syntaxError("Unterminated comment"); } this.pos = commentEnd + 2; continue; case '/': // skip a // end-of-line comment this.pos++; skipToEndOfLine(); continue; default: return c; } case '#': /* * Skip a # hash end-of-line comment. The JSON RFC doesn't specify this * behavior, but it's required to parse existing documents. See * http://b/2571423. */ skipToEndOfLine(); continue; default: return c; } } return -1; } /** * Advances the position until after the next newline character. If the line is * terminated by "\r\n", the '\n' must be consumed as whitespace by the caller. */ private void skipToEndOfLine() { for (; this.pos < this.in.length(); this.pos++) { char c = this.in.charAt(this.pos); if (c == '\r' || c == '\n') { this.pos++; break; } } } /** * Returns the string up to but not including {@code quote}, unescaping any character * escape sequences encountered along the way. The opening quote should have already * been read. This consumes the closing quote, but does not include it in the returned * string. * @param quote either ' or ". * @return the string up to but not including {@code quote} * @throws NumberFormatException if any unicode escape sequences are malformed. * @throws JSONException if processing of json failed */ public String nextString(char quote) throws JSONException { /* * For strings that are free of escape sequences, we can just extract the result * as a substring of the input. But if we encounter an escape sequence, we need to * use a StringBuilder to compose the result. */ StringBuilder builder = null; /* the index of the first character not yet appended to the builder. */ int start = this.pos; while (this.pos < this.in.length()) { int c = this.in.charAt(this.pos++); if (c == quote) { if (builder == null) { // a new string avoids leaking memory return new String(this.in.substring(start, this.pos - 1)); } else { builder.append(this.in, start, this.pos - 1); return builder.toString(); } } if (c == '\\') { if (this.pos == this.in.length()) { throw syntaxError("Unterminated escape sequence"); } if (builder == null) { builder = new StringBuilder(); } builder.append(this.in, start, this.pos - 1); builder.append(readEscapeCharacter()); start = this.pos; } } throw syntaxError("Unterminated string"); } /** * Unescapes the character identified by the character or characters that immediately * follow a backslash. The backslash '\' should have already been read. This supports * both unicode escapes "u000A" and two-character escapes "\n". * @return the unescaped char * @throws NumberFormatException if any unicode escape sequences are malformed. * @throws JSONException if processing of json failed */ private char readEscapeCharacter() throws JSONException { char escaped = this.in.charAt(this.pos++); switch (escaped) { case 'u': if (this.pos + 4 > this.in.length()) { throw syntaxError("Unterminated escape sequence"); } String hex = this.in.substring(this.pos, this.pos + 4); this.pos += 4; return (char) Integer.parseInt(hex, 16); case 't': return '\t'; case 'b': return '\b'; case 'n': return '\n'; case 'r': return '\r'; case 'f': return '\f'; case '\'': case '"': case '\\': default: return escaped; } } /** * Reads a null, boolean, numeric or unquoted string literal value. Numeric values * will be returned as an Integer, Long, or Double, in that order of preference. * @return a literal value * @throws JSONException if processing of json failed */ private Object readLiteral() throws JSONException { String literal = nextToInternal("{}[]/\\:,=;# \t\f"); if (literal.isEmpty()) { throw syntaxError("Expected literal value"); } else if ("null".equalsIgnoreCase(literal)) { return JSONObject.NULL; } else if ("true".equalsIgnoreCase(literal)) { return Boolean.TRUE; } else if ("false".equalsIgnoreCase(literal)) { return Boolean.FALSE; } /* try to parse as an integral type... */ if (literal.indexOf('.') == -1) { int base = 10; String number = literal; if (number.startsWith("0x") || number.startsWith("0X")) { number = number.substring(2); base = 16; } else if (number.startsWith("0") && number.length() > 1) { number = number.substring(1); base = 8; } try { long longValue = Long.parseLong(number, base); if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { return (int) longValue; } else { return longValue; } } catch (NumberFormatException e) { /* * This only happens for integral numbers greater than Long.MAX_VALUE, * numbers in exponential form (5e-10) and unquoted strings. Fall through * to try floating point. */ } } /* ...next try to parse as a floating point... */ try { return Double.valueOf(literal); } catch (NumberFormatException ignored) { } /* ... finally give up. We have an unquoted string */ return new String(literal); // a new string avoids leaking memory } /** * Returns the string up to but not including any of the given characters or a newline * character. This does not consume the excluded character. * @return the string up to but not including any of the given characters or a newline * character */ private String nextToInternal(String excluded) { int start = this.pos; for (; this.pos < this.in.length(); this.pos++) { char c = this.in.charAt(this.pos); if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { return this.in.substring(start, this.pos); } } return this.in.substring(start); } /** * Reads a sequence of key/value pairs and the trailing closing brace '}' of an * object. The opening brace '{' should have already been read. * @return an object * @throws JSONException if processing of json failed */ private JSONObject readObject() throws JSONException { JSONObject result = new JSONObject(); /* Peek to see if this is the empty object. */ int first = nextCleanInternal(); if (first == '}') { return result; } else if (first != -1) { this.pos--; } while (true) { Object name = nextValue(); if (!(name instanceof String)) { if (name == null) { throw syntaxError("Names cannot be null"); } else { throw syntaxError("Names must be strings, but " + name + " is of type " + name.getClass().getName()); } } /* * Expect the name/value separator to be either a colon ':', an equals sign * '=', or an arrow "=>". The last two are bogus but we include them because * that's what the original implementation did. */ int separator = nextCleanInternal(); if (separator != ':' && separator != '=') { throw syntaxError("Expected ':' after " + name); } if (this.pos < this.in.length() && this.in.charAt(this.pos) == '>') { this.pos++; } result.put((String) name, nextValue()); switch (nextCleanInternal()) { case '}': return result; case ';': case ',': continue; default: throw syntaxError("Unterminated object"); } } } /** * Reads a sequence of values and the trailing closing brace ']' of an array. The * opening brace '[' should have already been read. Note that "[]" yields an empty * array, but "[,]" returns a two-element array equivalent to "[null,null]". * @return an array * @throws JSONException if processing of json failed */ private JSONArray readArray() throws JSONException { JSONArray result = new JSONArray(); /* to cover input that ends with ",]". */ boolean hasTrailingSeparator = false; while (true) { switch (nextCleanInternal()) { case -1: throw syntaxError("Unterminated array"); case ']': if (hasTrailingSeparator) { result.put(null); } return result; case ',': case ';': /* A separator without a value first means "null". */ result.put(null); hasTrailingSeparator = true; continue; default: this.pos--; } result.put(nextValue()); switch (nextCleanInternal()) { case ']': return result; case ',': case ';': hasTrailingSeparator = true; continue; default: throw syntaxError("Unterminated array"); } } } /** * Returns an exception containing the given message plus the current position and the * entire input string. * @param message the message * @return an exception */ public JSONException syntaxError(String message) { return new JSONException(message + this); } /** * Returns the current position and the entire input string. * @return the current position and the entire input string. */ @Override public String toString() { // consistent with the original implementation return " at character " + this.pos + " of " + this.in; } /* * Legacy APIs. * * None of the methods below are on the critical path of parsing JSON documents. They * exist only because they were exposed by the original implementation and may be used * by some clients. */ public boolean more() { return this.pos < this.in.length(); } public char next() { return this.pos < this.in.length() ? this.in.charAt(this.pos++) : '\0'; } public char next(char c) throws JSONException { char result = next(); if (result != c) { throw syntaxError("Expected " + c + " but was " + result); } return result; } public char nextClean() throws JSONException { int nextCleanInt = nextCleanInternal(); return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; } public String next(int length) throws JSONException { if (this.pos + length > this.in.length()) { throw syntaxError(length + " is out of bounds"); } String result = this.in.substring(this.pos, this.pos + length); this.pos += length; return result; } public String nextTo(String excluded) { if (excluded == null) { throw new NullPointerException("excluded == null"); } return nextToInternal(excluded).trim(); } public String nextTo(char excluded) { return nextToInternal(String.valueOf(excluded)).trim(); } public void skipPast(String thru) { int thruStart = this.in.indexOf(thru, this.pos); this.pos = thruStart == -1 ? this.in.length() : (thruStart + thru.length()); } public char skipTo(char to) { int index = this.in.indexOf(to, this.pos); if (index != -1) { this.pos = index; return to; } else { return '\0'; } } public void back() { if (--this.pos == -1) { this.pos = 0; } } public static int dehexchar(char hex) { if (hex >= '0' && hex <= '9') { return hex - '0'; } else if (hex >= 'A' && hex <= 'F') { return hex - 'A' + 10; } else if (hex >= 'a' && hex <= 'f') { return hex - 'a' + 10; } else { return -1; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy