Share JSON library consistently
This commit shades the use of 'com.vaadin.external.google:android-json' in the three modules that use it. The configuration processor already did that and this commit does the same for configuration-metadata and the CLI. As a result of this commit, 'android-json' is not used nor managed internally. Closes gh-45504
This commit is contained in:
parent
4f18e5f1a8
commit
ed1ee79aef
|
@ -26,6 +26,14 @@ plugins {
|
||||||
|
|
||||||
description = "Spring Boot CLI"
|
description = "Spring Boot CLI"
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
java {
|
||||||
|
srcDir file("src/json-shade/java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
loader
|
loader
|
||||||
testRepository
|
testRepository
|
||||||
|
@ -37,7 +45,6 @@ dependencies {
|
||||||
compileOnlyProject(project(":core:spring-boot"))
|
compileOnlyProject(project(":core:spring-boot"))
|
||||||
|
|
||||||
implementation(project(":loader:spring-boot-loader-tools"))
|
implementation(project(":loader:spring-boot-loader-tools"))
|
||||||
implementation("com.vaadin.external.google:android-json")
|
|
||||||
implementation("jline:jline")
|
implementation("jline:jline")
|
||||||
implementation("net.sf.jopt-simple:jopt-simple")
|
implementation("net.sf.jopt-simple:jopt-simple")
|
||||||
implementation("org.apache.httpcomponents.client5:httpclient5")
|
implementation("org.apache.httpcomponents.client5:httpclient5")
|
||||||
|
@ -54,6 +61,10 @@ dependencies {
|
||||||
testImplementation(project(":test-support:spring-boot-test-support"))
|
testImplementation(project(":test-support:spring-boot-test-support"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
architectureCheck {
|
||||||
|
nullMarked = false
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register("fullJar", Jar) {
|
tasks.register("fullJar", Jar) {
|
||||||
dependsOn configurations.loader
|
dependsOn configurations.loader
|
||||||
archiveClassifier = "full"
|
archiveClassifier = "full"
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
## Shaded JSON
|
||||||
|
|
||||||
|
This source was originally taken from `com.vaadin.external.google:android-json` which
|
||||||
|
provides a clean room re-implementation of the `org.json` APIs and does not include the
|
||||||
|
"Do not use for evil" clause.
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
class JSON {
|
||||||
|
|
||||||
|
static double checkDouble(double d) throws JSONException {
|
||||||
|
if (Double.isInfinite(d) || Double.isNaN(d)) {
|
||||||
|
throw new JSONException("Forbidden numeric value: " + d);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Boolean toBoolean(Object value) {
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
if (value instanceof String stringValue) {
|
||||||
|
if ("true".equalsIgnoreCase(stringValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ("false".equalsIgnoreCase(stringValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Double toDouble(Object value) {
|
||||||
|
if (value instanceof Double) {
|
||||||
|
return (Double) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return Double.valueOf((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Integer toInteger(Object value) {
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return (int) Double.parseDouble((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Long toLong(Object value) {
|
||||||
|
if (value instanceof Long) {
|
||||||
|
return (Long) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return (long) Double.parseDouble((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toString(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONException typeMismatch(Object indexOrName, Object actual, String requiredType)
|
||||||
|
throws JSONException {
|
||||||
|
if (actual == null) {
|
||||||
|
throw new JSONException("Value at " + indexOrName + " is null.");
|
||||||
|
}
|
||||||
|
throw new JSONException("Value " + actual + " at " + indexOrName + " of type " + actual.getClass().getName()
|
||||||
|
+ " cannot be converted to " + requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONException typeMismatch(Object actual, String requiredType) throws JSONException {
|
||||||
|
if (actual == null) {
|
||||||
|
throw new JSONException("Value is null.");
|
||||||
|
}
|
||||||
|
throw new JSONException("Value " + actual + " of type " + actual.getClass().getName()
|
||||||
|
+ " cannot be converted to " + requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,669 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dense indexed sequence of values. Values may be any mix of {@link JSONObject
|
||||||
|
* JSONObjects}, other {@link JSONArray JSONArrays}, Strings, Booleans, Integers, Longs,
|
||||||
|
* Doubles, {@code null} or {@link JSONObject#NULL}. Values may not be
|
||||||
|
* {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not
|
||||||
|
* listed here.
|
||||||
|
* <p>
|
||||||
|
* {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors
|
||||||
|
* as {@link JSONObject}. See that class' documentation for details.
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> this class represents null in two incompatible ways: the
|
||||||
|
* standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}.
|
||||||
|
* In particular, {@code get} fails if the requested index holds the null reference, but
|
||||||
|
* succeeds if it holds {@code JSONObject.NULL}.
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
* overridable methods is not specified. See <i>Effective Java</i> Item 17, "Design and
|
||||||
|
* Document or inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONArray {
|
||||||
|
|
||||||
|
private final List<Object> values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code JSONArray} with no values.
|
||||||
|
*/
|
||||||
|
public JSONArray() {
|
||||||
|
this.values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} by copying all values from the given collection.
|
||||||
|
* @param copyFrom a collection whose values are of supported types. Unsupported
|
||||||
|
* values are not permitted and will yield an array in an inconsistent state.
|
||||||
|
*/
|
||||||
|
/* Accept a raw type for API compatibility */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public JSONArray(Collection copyFrom) {
|
||||||
|
this();
|
||||||
|
if (copyFrom != null) {
|
||||||
|
for (Iterator it = copyFrom.iterator(); it.hasNext();) {
|
||||||
|
put(JSONObject.wrap(it.next()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the next array in the tokener.
|
||||||
|
* @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray}.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code JSONArray}.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray(JSONTokener readFrom) throws JSONException {
|
||||||
|
/*
|
||||||
|
* Getting the parser to populate this could get tricky. Instead, just parse to
|
||||||
|
* temporary JSONArray and then steal the data from that.
|
||||||
|
*/
|
||||||
|
Object object = readFrom.nextValue();
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
this.values = ((JSONArray) object).values;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the JSON string.
|
||||||
|
* @param json a JSON-encoded string containing an array.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray(String json) throws JSONException {
|
||||||
|
this(new JSONTokener(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the given primitive array.
|
||||||
|
* @param array a primitive array
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray(Object array) throws JSONException {
|
||||||
|
if (!array.getClass().isArray()) {
|
||||||
|
throw new JSONException("Not a primitive array: " + array.getClass());
|
||||||
|
}
|
||||||
|
final int length = Array.getLength(array);
|
||||||
|
this.values = new ArrayList<>(length);
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
put(JSONObject.wrap(Array.get(array, i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values in this array.
|
||||||
|
* @return the length of this array
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
return this.values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(boolean value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(double value) throws JSONException {
|
||||||
|
this.values.add(JSON.checkDouble(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(int value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(long value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be
|
||||||
|
* {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. Unsupported
|
||||||
|
* values are not permitted and will cause the array to be in an inconsistent state.
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(Object value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, boolean value) throws JSONException {
|
||||||
|
return put(index, (Boolean) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, double value) throws JSONException {
|
||||||
|
return put(index, (Double) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, int value) throws JSONException {
|
||||||
|
return put(index, (Integer) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, long value) throws JSONException {
|
||||||
|
return put(index, (Long) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be
|
||||||
|
* {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, Object value) throws JSONException {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
// deviate from the original by checking all Numbers, not just floats &
|
||||||
|
// doubles
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
while (this.values.size() <= index) {
|
||||||
|
this.values.add(null);
|
||||||
|
}
|
||||||
|
this.values.set(index, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this array has no value at {@code index}, or if its value is the
|
||||||
|
* {@code null} reference or {@link JSONObject#NULL}.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @return true if this array has no value at {@code index}
|
||||||
|
*/
|
||||||
|
public boolean isNull(int index) {
|
||||||
|
Object value = opt(index);
|
||||||
|
return value == null || value == JSONObject.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index}.
|
||||||
|
* @throws JSONException if this array has no value at {@code index}, or if that value
|
||||||
|
* is the {@code null} reference. This method returns normally if the value is
|
||||||
|
* {@code JSONObject#NULL}.
|
||||||
|
*/
|
||||||
|
public Object get(int index) throws JSONException {
|
||||||
|
try {
|
||||||
|
Object value = this.values.get(index);
|
||||||
|
if (value == null) {
|
||||||
|
throw new JSONException("Value at " + index + " is null.");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new JSONException("Index " + index + " out of range [0.." + this.values.size() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index}, or null if the array has no value at
|
||||||
|
* {@code index}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public Object opt(int index) {
|
||||||
|
if (index < 0 || index >= this.values.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.values.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the value at {@code index}, or null if the array has no value
|
||||||
|
* at {@code index}.
|
||||||
|
* @param index the index of the value to remove
|
||||||
|
* @return the previous value at {@code index}
|
||||||
|
*/
|
||||||
|
public Object remove(int index) {
|
||||||
|
if (index < 0 || index >= this.values.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.values.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a boolean.
|
||||||
|
*/
|
||||||
|
public boolean getBoolean(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "boolean");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean. Returns false otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code false}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(int index) {
|
||||||
|
return optBoolean(index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(int index, boolean fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a double.
|
||||||
|
*/
|
||||||
|
public double getDouble(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "double");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double. Returns {@code NaN} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code NaN}
|
||||||
|
*/
|
||||||
|
public double optDouble(int index) {
|
||||||
|
return optDouble(index, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public double optDouble(int index, double fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to an int.
|
||||||
|
*/
|
||||||
|
public int getInt(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "int");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int. Returns 0 otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code 0}
|
||||||
|
*/
|
||||||
|
public int optInt(int index) {
|
||||||
|
return optInt(index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public int optInt(int index, int fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a long.
|
||||||
|
*/
|
||||||
|
public long getLong(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "long");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long. Returns 0 otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code 0}
|
||||||
|
*/
|
||||||
|
public long optLong(int index) {
|
||||||
|
return optLong(index, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public long optLong(int index, long fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if no such value exists.
|
||||||
|
*/
|
||||||
|
public String getString(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "String");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary. Returns
|
||||||
|
* the empty string if no such value exists.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or an empty string
|
||||||
|
*/
|
||||||
|
public String optString(int index) {
|
||||||
|
return optString(index, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary. Returns
|
||||||
|
* {@code fallback} if no such value exists.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public String optString(int index, String fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the array at {@code index}
|
||||||
|
* @throws JSONException if the value doesn't exist or is not a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray getJSONArray(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
return (JSONArray) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(index, object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONArray}. Returns null otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the array at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONArray optJSONArray(int index) {
|
||||||
|
Object object = opt(index);
|
||||||
|
return object instanceof JSONArray ? (JSONArray) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the object at {@code index}
|
||||||
|
* @throws JSONException if the value doesn't exist or is not a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject getJSONObject(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
return (JSONObject) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(index, object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONObject}. Returns null otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the object at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONObject optJSONObject(int index) {
|
||||||
|
Object object = opt(index);
|
||||||
|
return object instanceof JSONObject ? (JSONObject) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new object whose values are the values in this array, and whose names are
|
||||||
|
* the values in {@code names}. Names and values are paired up by index from 0 through
|
||||||
|
* to the shorter array's length. Names that are not strings will be coerced to
|
||||||
|
* strings. This method returns null if either array is empty.
|
||||||
|
* @param names the property names
|
||||||
|
* @return a json object
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONObject toJSONObject(JSONArray names) throws JSONException {
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
int length = Math.min(names.length(), this.values.size());
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
String name = JSON.toString(names.opt(i));
|
||||||
|
result.put(name, opt(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new string by alternating this array's values with {@code
|
||||||
|
* separator}. This array's string values are quoted and have their special characters
|
||||||
|
* escaped. For example, the array containing the strings '12" pizza', 'taco' and
|
||||||
|
* 'soda' joined on '+' returns this: <pre>"12\" pizza"+"taco"+"soda"</pre>
|
||||||
|
* @param separator the separator to use
|
||||||
|
* @return the joined value
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public String join(String separator) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
stringer.open(JSONStringer.Scope.NULL, "");
|
||||||
|
for (int i = 0, size = this.values.size(); i < size; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
stringer.out.append(separator);
|
||||||
|
}
|
||||||
|
stringer.value(this.values.get(i));
|
||||||
|
}
|
||||||
|
stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
|
||||||
|
return stringer.out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this array as a compact JSON string, such as: <pre>[94043,90210]</pre>
|
||||||
|
* @return a compact JSON string representation of this array
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this array as a human-readable JSON string for debugging, such as: <pre>
|
||||||
|
* [
|
||||||
|
* 94043,
|
||||||
|
* 90210
|
||||||
|
* ]</pre>
|
||||||
|
* @param indentSpaces the number of spaces to indent for each level of nesting.
|
||||||
|
* @return a human-readable JSON string of this array
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public String toString(int indentSpaces) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer(indentSpaces);
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeTo(JSONStringer stringer) throws JSONException {
|
||||||
|
stringer.array();
|
||||||
|
for (Object value : this.values) {
|
||||||
|
stringer.value(value);
|
||||||
|
}
|
||||||
|
stringer.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof JSONArray && ((JSONArray) o).values.equals(this.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// diverge from the original, which doesn't implement hashCode
|
||||||
|
return this.values.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown to indicate a problem with the JSON API. Such problems include:
|
||||||
|
* <ul>
|
||||||
|
* <li>Attempts to parse or construct malformed documents
|
||||||
|
* <li>Use of null as a name
|
||||||
|
* <li>Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* <li>Lookups using an out of range index or nonexistent name
|
||||||
|
* <li>Type mismatches on lookups
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Although this is a checked exception, it is rarely recoverable. Most callers should
|
||||||
|
* simply wrap this exception in an unchecked exception and rethrow: <pre class="code">
|
||||||
|
* public JSONArray toJSONObject() {
|
||||||
|
* try {
|
||||||
|
* JSONObject result = new JSONObject();
|
||||||
|
* ...
|
||||||
|
* } catch (JSONException e) {
|
||||||
|
* throw new RuntimeException(e);
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public class JSONException extends Exception {
|
||||||
|
|
||||||
|
public JSONException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,836 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modifiable set of name/value mappings. Names are unique, non-null strings. Values may
|
||||||
|
* be any mix of {@link JSONObject JSONObjects}, {@link JSONArray JSONArrays}, Strings,
|
||||||
|
* Booleans, Integers, Longs, Doubles or {@link #NULL}. Values may not be {@code null},
|
||||||
|
* {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not
|
||||||
|
* listed here.
|
||||||
|
* <p>
|
||||||
|
* This class can coerce values to another type when requested.
|
||||||
|
* <ul>
|
||||||
|
* <li>When the requested type is a boolean, strings will be coerced using a
|
||||||
|
* case-insensitive comparison to "true" and "false".
|
||||||
|
* <li>When the requested type is a double, other {@link Number} types will be coerced
|
||||||
|
* using {@link Number#doubleValue() doubleValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be.
|
||||||
|
* <li>When the requested type is an int, other {@link Number} types will be coerced using
|
||||||
|
* {@link Number#intValue() intValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be, and then cast to int.
|
||||||
|
* <li><a id="lossy">When the requested type is a long, other {@link Number} types will be
|
||||||
|
* coerced using {@link Number#longValue() longValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be, and then cast to long. This two-step conversion
|
||||||
|
* is lossy for very large values. For example, the string "9223372036854775806" yields
|
||||||
|
* the long 9223372036854775807.</a>
|
||||||
|
* <li>When the requested type is a String, other non-null values will be coerced using
|
||||||
|
* {@link String#valueOf(Object)}. Although null cannot be coerced, the sentinel value
|
||||||
|
* {@link JSONObject#NULL} is coerced to the string "null".
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* This class can look up both mandatory and optional values:
|
||||||
|
* <ul>
|
||||||
|
* <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This fails with a
|
||||||
|
* {@code JSONException} if the requested name has no value or if the value cannot be
|
||||||
|
* coerced to the requested type.
|
||||||
|
* <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This returns a
|
||||||
|
* system- or user-supplied default if the requested name has no value or if the value
|
||||||
|
* cannot be coerced to the requested type.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> this class represents null in two incompatible ways: the
|
||||||
|
* standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}.
|
||||||
|
* In particular, calling {@code put(name, null)} removes the named entry from the object
|
||||||
|
* but {@code put(name, JSONObject.NULL)} stores an entry whose value is
|
||||||
|
* {@code JSONObject.NULL}.
|
||||||
|
* <p>
|
||||||
|
* 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 <i>Effective Java</i> Item 17, "Design and
|
||||||
|
* Document or inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONObject {
|
||||||
|
|
||||||
|
private static final Double NEGATIVE_ZERO = -0d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sentinel value used to explicitly define a name with no value. Unlike
|
||||||
|
* {@code null}, names with this value:
|
||||||
|
* <ul>
|
||||||
|
* <li>show up in the {@link #names} array
|
||||||
|
* <li>show up in the {@link #keys} iterator
|
||||||
|
* <li>return {@code true} for {@link #has(String)}
|
||||||
|
* <li>do not throw on {@link #get(String)}
|
||||||
|
* <li>are included in the encoded JSON string.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* This value violates the general contract of {@link Object#equals} by returning true
|
||||||
|
* when compared to {@code null}. Its {@link #toString} method returns "null".
|
||||||
|
*/
|
||||||
|
public static final Object NULL = new Object() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o == this || o == null; // API specifies this broken equals
|
||||||
|
// implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Map<String, Object> nameValuePairs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code JSONObject} with no name/value mappings.
|
||||||
|
*/
|
||||||
|
public JSONObject() {
|
||||||
|
this.nameValuePairs = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} by copying all name/value mappings from the given
|
||||||
|
* map.
|
||||||
|
* @param copyFrom a map whose keys are of type {@link String} and whose values are of
|
||||||
|
* supported types.
|
||||||
|
* @throws NullPointerException if any of the map's keys are null.
|
||||||
|
*/
|
||||||
|
/* (accept a raw type for API compatibility) */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public JSONObject(Map copyFrom) {
|
||||||
|
this();
|
||||||
|
Map<?, ?> contentsTyped = copyFrom;
|
||||||
|
for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
|
||||||
|
/*
|
||||||
|
* Deviate from the original by checking that keys are non-null and of the
|
||||||
|
* proper type. (We still defer validating the values).
|
||||||
|
*/
|
||||||
|
String key = (String) entry.getKey();
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
this.nameValuePairs.put(key, wrap(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} with name/value mappings from the next object in
|
||||||
|
* the tokener.
|
||||||
|
* @param readFrom a tokener whose nextValue() method will yield a {@code JSONObject}.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject(JSONTokener readFrom) throws JSONException {
|
||||||
|
/*
|
||||||
|
* Getting the parser to populate this could get tricky. Instead, just parse to
|
||||||
|
* temporary JSONObject and then steal the data from that.
|
||||||
|
*/
|
||||||
|
Object object = readFrom.nextValue();
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
this.nameValuePairs = ((JSONObject) object).nameValuePairs;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} with name/value mappings from the JSON string.
|
||||||
|
* @param json a JSON-encoded string containing an object.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject(String json) throws JSONException {
|
||||||
|
this(new JSONTokener(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} by copying mappings for the listed names from the
|
||||||
|
* given object. Names that aren't present in {@code copyFrom} will be skipped.
|
||||||
|
* @param copyFrom the source
|
||||||
|
* @param names the property names
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject(JSONObject copyFrom, String[] names) throws JSONException {
|
||||||
|
this();
|
||||||
|
for (String name : names) {
|
||||||
|
Object value = copyFrom.opt(name);
|
||||||
|
if (value != null) {
|
||||||
|
this.nameValuePairs.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of name/value mappings in this object.
|
||||||
|
* @return the number of name/value mappings in this object
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
return this.nameValuePairs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, boolean value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, double value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), JSON.checkDouble(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, int value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, long value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name. If the value is {@code null}, any existing mapping for {@code name}
|
||||||
|
* is removed.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link #NULL}, or {@code null}. May not be {@link Double#isNaN()
|
||||||
|
* NaNs} or {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, Object value) throws JSONException {
|
||||||
|
if (value == null) {
|
||||||
|
this.nameValuePairs.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
// deviate from the original by checking all Numbers, not just floats &
|
||||||
|
// doubles
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code put(name, value)} when both parameters are non-null; does
|
||||||
|
* nothing otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject putOpt(String name, Object value) throws JSONException {
|
||||||
|
if (name == null || value == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the array already mapped to {@code name}. If this object
|
||||||
|
* has no mapping for {@code name}, this inserts a new mapping. If the mapping exists
|
||||||
|
* but its value is not an array, the existing and new values are inserted in order
|
||||||
|
* into a new array which is itself mapped to {@code name}. In aggregate, this allows
|
||||||
|
* values to be added to a mapping one at a time.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link #NULL} or null. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject accumulate(String name, Object value) throws JSONException {
|
||||||
|
Object current = this.nameValuePairs.get(checkName(name));
|
||||||
|
if (current == null) {
|
||||||
|
return put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check in accumulate, since array.put(Object) doesn't do any checking
|
||||||
|
if (value instanceof Number) {
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current instanceof JSONArray array) {
|
||||||
|
array.put(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
array.put(current);
|
||||||
|
array.put(value);
|
||||||
|
this.nameValuePairs.put(name, array);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String checkName(String name) throws JSONException {
|
||||||
|
if (name == null) {
|
||||||
|
throw new JSONException("Names must be non-null");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the named mapping if it exists; does nothing otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value previously mapped by {@code name}, or null if there was no such
|
||||||
|
* mapping.
|
||||||
|
*/
|
||||||
|
public Object remove(String name) {
|
||||||
|
return this.nameValuePairs.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this object has no mapping for {@code name} or if it has a mapping
|
||||||
|
* whose value is {@link #NULL}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return true if this object has no mapping for {@code name}
|
||||||
|
*/
|
||||||
|
public boolean isNull(String name) {
|
||||||
|
Object value = this.nameValuePairs.get(name);
|
||||||
|
return value == null || value == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this object has a mapping for {@code name}. The mapping may be
|
||||||
|
* {@link #NULL}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return true if this object has a mapping for {@code name}
|
||||||
|
*/
|
||||||
|
public boolean has(String name) {
|
||||||
|
return this.nameValuePairs.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if no such mapping exists.
|
||||||
|
*/
|
||||||
|
public Object get(String name) throws JSONException {
|
||||||
|
Object result = this.nameValuePairs.get(name);
|
||||||
|
if (result == null) {
|
||||||
|
throw new JSONException("No value for " + name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name}, or null if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public Object opt(String name) {
|
||||||
|
return this.nameValuePairs.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a
|
||||||
|
* boolean.
|
||||||
|
*/
|
||||||
|
public boolean getBoolean(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "boolean");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean. Returns false otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(String name) {
|
||||||
|
return optBoolean(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(String name, boolean fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a
|
||||||
|
* double.
|
||||||
|
*/
|
||||||
|
public double getDouble(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "double");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double. Returns {@code NaN} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code NaN}
|
||||||
|
*/
|
||||||
|
public double optDouble(String name) {
|
||||||
|
return optDouble(name, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public double optDouble(String name, double fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to an int.
|
||||||
|
*/
|
||||||
|
public int getInt(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "int");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int. Returns 0 otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value of {@code 0}
|
||||||
|
*/
|
||||||
|
public int optInt(String name) {
|
||||||
|
return optInt(name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public int optInt(String name, int fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Note that JSON represents numbers as doubles, so this is
|
||||||
|
* <a href="#lossy">lossy</a>; use strings to transfer numbers over JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a long.
|
||||||
|
*/
|
||||||
|
public long getLong(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "long");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as
|
||||||
|
* doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via
|
||||||
|
* JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code 0L}
|
||||||
|
*/
|
||||||
|
public long optLong(String name) {
|
||||||
|
return optLong(name, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents
|
||||||
|
* numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
|
||||||
|
* numbers over JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public long optLong(String name, long fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if no such mapping exists.
|
||||||
|
*/
|
||||||
|
public String getString(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "String");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* Returns the empty string if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or an empty string
|
||||||
|
*/
|
||||||
|
public String optString(String name) {
|
||||||
|
return optString(name, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* Returns {@code fallback} if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public String optString(String name, String fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray getJSONArray(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
return (JSONArray) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(name, object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONArray}. Returns null otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONArray optJSONArray(String name) {
|
||||||
|
Object object = opt(name);
|
||||||
|
return object instanceof JSONArray ? (JSONArray) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject getJSONObject(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
return (JSONObject) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(name, object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONObject}. Returns null otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONObject optJSONObject(String name) {
|
||||||
|
Object object = opt(name);
|
||||||
|
return object instanceof JSONObject ? (JSONObject) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with the values corresponding to {@code names}. The array contains
|
||||||
|
* null for names that aren't mapped. This method returns null if {@code names} is
|
||||||
|
* either null or empty.
|
||||||
|
* @param names the names of the properties
|
||||||
|
* @return the array
|
||||||
|
*/
|
||||||
|
public JSONArray toJSONArray(JSONArray names) {
|
||||||
|
JSONArray result = new JSONArray();
|
||||||
|
if (names == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int length = names.length();
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
String name = JSON.toString(names.opt(i));
|
||||||
|
result.put(opt(name));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator of the {@code String} names in this object. The returned
|
||||||
|
* iterator supports {@link Iterator#remove() remove}, which will remove the
|
||||||
|
* corresponding mapping from this object. If this object is modified after the
|
||||||
|
* iterator is returned, the iterator's behavior is undefined. The order of the keys
|
||||||
|
* is undefined.
|
||||||
|
* @return the keys
|
||||||
|
*/
|
||||||
|
/* Return a raw type for API compatibility */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public Iterator keys() {
|
||||||
|
return this.nameValuePairs.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing the string names in this object. This method returns
|
||||||
|
* null if this object contains no mappings.
|
||||||
|
* @return the array
|
||||||
|
*/
|
||||||
|
public JSONArray names() {
|
||||||
|
return this.nameValuePairs.isEmpty() ? null : new JSONArray(new ArrayList<>(this.nameValuePairs.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this object as a compact JSON string, such as:
|
||||||
|
* <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
|
||||||
|
* @return a string representation of the object.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this object as a human-readable JSON string for debugging, such as: <pre>
|
||||||
|
* {
|
||||||
|
* "query": "Pizza",
|
||||||
|
* "locations": [
|
||||||
|
* 94043,
|
||||||
|
* 90210
|
||||||
|
* ]
|
||||||
|
* }</pre>
|
||||||
|
* @param indentSpaces the number of spaces to indent for each level of nesting.
|
||||||
|
* @return a string representation of the object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public String toString(int indentSpaces) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer(indentSpaces);
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeTo(JSONStringer stringer) throws JSONException {
|
||||||
|
stringer.object();
|
||||||
|
for (Map.Entry<String, Object> entry : this.nameValuePairs.entrySet()) {
|
||||||
|
stringer.key(entry.getKey()).value(entry.getValue());
|
||||||
|
}
|
||||||
|
stringer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the number as a JSON string.
|
||||||
|
* @param number a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return the encoded value
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public static String numberToString(Number number) throws JSONException {
|
||||||
|
if (number == null) {
|
||||||
|
throw new JSONException("Number must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
double doubleValue = number.doubleValue();
|
||||||
|
JSON.checkDouble(doubleValue);
|
||||||
|
|
||||||
|
// the original returns "-0" instead of "-0.0" for negative zero
|
||||||
|
if (number.equals(NEGATIVE_ZERO)) {
|
||||||
|
return "-0";
|
||||||
|
}
|
||||||
|
|
||||||
|
long longValue = number.longValue();
|
||||||
|
if (doubleValue == longValue) {
|
||||||
|
return Long.toString(longValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return number.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code data} as a JSON string. This applies quotes and any necessary
|
||||||
|
* character escaping.
|
||||||
|
* @param data the string to encode. Null will be interpreted as an empty string.
|
||||||
|
* @return the quoted value
|
||||||
|
*/
|
||||||
|
public static String quote(String data) {
|
||||||
|
if (data == null) {
|
||||||
|
return "\"\"";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
stringer.open(JSONStringer.Scope.NULL, "");
|
||||||
|
stringer.value(data);
|
||||||
|
stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the given object if necessary.
|
||||||
|
* <p>
|
||||||
|
* If the object is null or, returns {@link #NULL}. If the object is a
|
||||||
|
* {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. If the object is
|
||||||
|
* {@code NULL}, no wrapping is necessary. If the object is an array or
|
||||||
|
* {@code Collection}, returns an equivalent {@code JSONArray}. If the object is a
|
||||||
|
* {@code Map}, returns an equivalent {@code JSONObject}. If the object is a primitive
|
||||||
|
* wrapper type or {@code String}, returns the object. Otherwise if the object is from
|
||||||
|
* a {@code java} package, returns the result of {@code toString}. If wrapping fails,
|
||||||
|
* returns null.
|
||||||
|
* @param o the object to wrap
|
||||||
|
* @return the wrapped object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static Object wrap(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (o instanceof JSONArray || o instanceof JSONObject) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
if (o.equals(NULL)) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (o instanceof Collection) {
|
||||||
|
return new JSONArray((Collection) o);
|
||||||
|
}
|
||||||
|
else if (o.getClass().isArray()) {
|
||||||
|
return new JSONArray(o);
|
||||||
|
}
|
||||||
|
if (o instanceof Map) {
|
||||||
|
return new JSONObject((Map) o);
|
||||||
|
}
|
||||||
|
if (o instanceof Boolean || o instanceof Byte || o instanceof Character || o instanceof Double
|
||||||
|
|| o instanceof Float || o instanceof Integer || o instanceof Long || o instanceof Short
|
||||||
|
|| o instanceof String) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
if (o.getClass().getPackage().getName().startsWith("java.")) {
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,429 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most application
|
||||||
|
* developers should use those methods directly and disregard this API. For example:<pre>
|
||||||
|
* JSONObject object = ...
|
||||||
|
* String json = object.toString();</pre>
|
||||||
|
* <p>
|
||||||
|
* Stringers only encode well-formed JSON strings. In particular:
|
||||||
|
* <ul>
|
||||||
|
* <li>The stringer must have exactly one top-level array or object.
|
||||||
|
* <li>Lexical scopes must be balanced: every call to {@link #array} must have a matching
|
||||||
|
* call to {@link #endArray} and every call to {@link #object} must have a matching call
|
||||||
|
* to {@link #endObject}.
|
||||||
|
* <li>Arrays may not contain keys (property names).
|
||||||
|
* <li>Objects must alternate keys (property names) and values.
|
||||||
|
* <li>Values are inserted with either literal {@link #value(Object) value} calls, or by
|
||||||
|
* nesting arrays or objects.
|
||||||
|
* </ul>
|
||||||
|
* Calls that would result in a malformed JSON string will fail with a
|
||||||
|
* {@link JSONException}.
|
||||||
|
* <p>
|
||||||
|
* This class provides no facility for pretty-printing (ie. indenting) output. To encode
|
||||||
|
* indented output, use {@link JSONObject#toString(int)} or
|
||||||
|
* {@link JSONArray#toString(int)}.
|
||||||
|
* <p>
|
||||||
|
* Some implementations of the API support at most 20 levels of nesting. Attempts to
|
||||||
|
* create more than 20 levels of nesting may fail with a {@link JSONException}.
|
||||||
|
* <p>
|
||||||
|
* Each stringer may be used to encode a single top level value. 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 <i>Effective Java</i> Item 17, "Design and Document or
|
||||||
|
* inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONStringer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output data, containing at most one top-level array or object.
|
||||||
|
*/
|
||||||
|
final StringBuilder out = new StringBuilder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexical scoping elements within this stringer, necessary to insert the appropriate
|
||||||
|
* separator characters (i.e. commas and colons) and to detect nesting errors.
|
||||||
|
*/
|
||||||
|
enum Scope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array with no elements requires no separators or newlines before it is
|
||||||
|
* closed.
|
||||||
|
*/
|
||||||
|
EMPTY_ARRAY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array with at least one value requires a comma and newline before the next
|
||||||
|
* element.
|
||||||
|
*/
|
||||||
|
NONEMPTY_ARRAY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with no keys or values requires no separators or newlines before it
|
||||||
|
* is closed.
|
||||||
|
*/
|
||||||
|
EMPTY_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object whose most recent element is a key. The next element must be a value.
|
||||||
|
*/
|
||||||
|
DANGLING_KEY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with at least one name/value pair requires a comma and newline before
|
||||||
|
* the next element.
|
||||||
|
*/
|
||||||
|
NONEMPTY_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special bracketless array needed by JSONStringer.join() and
|
||||||
|
* JSONObject.quote() only. Not used for JSON encoding.
|
||||||
|
*/
|
||||||
|
NULL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlike the original implementation, this stack isn't limited to 20 levels of
|
||||||
|
* nesting.
|
||||||
|
*/
|
||||||
|
private final List<Scope> stack = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string containing a full set of spaces for a single level of indentation, or null
|
||||||
|
* for no pretty printing.
|
||||||
|
*/
|
||||||
|
private final String indent;
|
||||||
|
|
||||||
|
public JSONStringer() {
|
||||||
|
this.indent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONStringer(int indentSpaces) {
|
||||||
|
char[] indentChars = new char[indentSpaces];
|
||||||
|
Arrays.fill(indentChars, ' ');
|
||||||
|
this.indent = new String(indentChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new array. Each call to this method must be paired with a call to
|
||||||
|
* {@link #endArray}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer array() throws JSONException {
|
||||||
|
return open(Scope.EMPTY_ARRAY, "[");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current array.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer endArray() throws JSONException {
|
||||||
|
return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new object. Each call to this method must be paired with a call
|
||||||
|
* to {@link #endObject}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer object() throws JSONException {
|
||||||
|
return open(Scope.EMPTY_OBJECT, "{");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current object.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer endObject() throws JSONException {
|
||||||
|
return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters a new scope by appending any necessary whitespace and the given bracket.
|
||||||
|
* @param empty any necessary whitespace
|
||||||
|
* @param openBracket the open bracket
|
||||||
|
* @return this object
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
JSONStringer open(Scope empty, String openBracket) throws JSONException {
|
||||||
|
if (this.stack.isEmpty() && !this.out.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem: multiple top-level roots");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.stack.add(empty);
|
||||||
|
this.out.append(openBracket);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current scope by appending any necessary whitespace and the given
|
||||||
|
* bracket.
|
||||||
|
* @param empty any necessary whitespace
|
||||||
|
* @param nonempty the current scope
|
||||||
|
* @param closeBracket the close bracket
|
||||||
|
* @return the JSON stringer
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
|
||||||
|
Scope context = peek();
|
||||||
|
if (context != nonempty && context != empty) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stack.remove(this.stack.size() - 1);
|
||||||
|
if (context == nonempty) {
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
this.out.append(closeBracket);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value on the top of the stack.
|
||||||
|
* @return the scope
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private Scope peek() throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
return this.stack.get(this.stack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the value on the top of the stack with the given value.
|
||||||
|
* @param topOfStack the scope at the top of the stack
|
||||||
|
*/
|
||||||
|
private void replaceTop(Scope topOfStack) {
|
||||||
|
this.stack.set(this.stack.size() - 1, topOfStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value}.
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double or null. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(Object value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof JSONArray) {
|
||||||
|
((JSONArray) value).writeTo(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (value instanceof JSONObject) {
|
||||||
|
((JSONObject) value).writeTo(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeValue();
|
||||||
|
|
||||||
|
if (value == null || value instanceof Boolean || value == JSONObject.NULL) {
|
||||||
|
this.out.append(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (value instanceof Number) {
|
||||||
|
this.out.append(JSONObject.numberToString((Number) value));
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value the value to encode
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(boolean value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(double value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(JSONObject.numberToString(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value the value to encode
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(long value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void string(String value) {
|
||||||
|
this.out.append("\"");
|
||||||
|
for (int i = 0, length = value.length(); i < length; i++) {
|
||||||
|
char c = value.charAt(i);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From RFC 4627, "All Unicode characters may be placed within the quotation
|
||||||
|
* marks except for the characters that must be escaped: quotation mark,
|
||||||
|
* reverse solidus, and the control characters (U+0000 through U+001F)."
|
||||||
|
*/
|
||||||
|
switch (c) {
|
||||||
|
case '"', '\\', '/' -> this.out.append('\\').append(c);
|
||||||
|
case '\t' -> this.out.append("\\t");
|
||||||
|
case '\b' -> this.out.append("\\b");
|
||||||
|
case '\n' -> this.out.append("\\n");
|
||||||
|
case '\r' -> this.out.append("\\r");
|
||||||
|
case '\f' -> this.out.append("\\f");
|
||||||
|
default -> {
|
||||||
|
if (c <= 0x1F) {
|
||||||
|
this.out.append(String.format("\\u%04x", (int) c));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.out.append("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void newline() {
|
||||||
|
if (this.indent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.out.append("\n");
|
||||||
|
this.out.append(this.indent.repeat(this.stack.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the key (property name) to this stringer.
|
||||||
|
* @param name the name of the forthcoming value. May not be null.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer key(String name) throws JSONException {
|
||||||
|
if (name == null) {
|
||||||
|
throw new JSONException("Names must be non-null");
|
||||||
|
}
|
||||||
|
beforeKey();
|
||||||
|
string(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts any necessary separators and whitespace before a name. Also adjusts the
|
||||||
|
* stack to expect the key's value.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private void beforeKey() throws JSONException {
|
||||||
|
Scope context = peek();
|
||||||
|
if (context == Scope.NONEMPTY_OBJECT) { // first in object
|
||||||
|
this.out.append(',');
|
||||||
|
}
|
||||||
|
else if (context != Scope.EMPTY_OBJECT) { // not in an object!
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
newline();
|
||||||
|
replaceTop(Scope.DANGLING_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts any necessary separators and whitespace before a literal value, inline
|
||||||
|
* array, or inline object. Also adjusts the stack to expect either a closing bracket
|
||||||
|
* or another element.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private void beforeValue() throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope context = peek();
|
||||||
|
if (context == Scope.EMPTY_ARRAY) { // first in array
|
||||||
|
replaceTop(Scope.NONEMPTY_ARRAY);
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
else if (context == Scope.NONEMPTY_ARRAY) { // another in array
|
||||||
|
this.out.append(',');
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
else if (context == Scope.DANGLING_KEY) { // value for key
|
||||||
|
this.out.append(this.indent == null ? ":" : ": ");
|
||||||
|
replaceTop(Scope.NONEMPTY_OBJECT);
|
||||||
|
}
|
||||||
|
else if (context != Scope.NULL) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded JSON string.
|
||||||
|
* <p>
|
||||||
|
* If invoked with unterminated arrays or unclosed objects, this method's return value
|
||||||
|
* is undefined.
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> although it contradicts the general contract of
|
||||||
|
* {@link Object#toString}, this method returns null if the stringer contains no data.
|
||||||
|
* @return the encoded JSON string.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.out.isEmpty() ? null : this.out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,555 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.cli.json;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a JSON (<a href="https://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 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:
|
||||||
|
* <pre>
|
||||||
|
* String json = "{"
|
||||||
|
* + " \"query\": \"Pizza\", "
|
||||||
|
* + " \"locations\": [ 94043, 90210 ] "
|
||||||
|
* + "}";
|
||||||
|
*
|
||||||
|
* JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
|
||||||
|
* String query = object.getString("query");
|
||||||
|
* JSONArray locations = object.getJSONArray("locations");</pre>
|
||||||
|
* <p>
|
||||||
|
* 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 the
|
||||||
|
* following syntax errors will be ignored:
|
||||||
|
* <ul>
|
||||||
|
* <li>End of line comments starting with {@code //} or {@code #} and ending with a
|
||||||
|
* newline character.
|
||||||
|
* <li>C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such
|
||||||
|
* comments may not be nested.
|
||||||
|
* <li>Strings that are unquoted or {@code 'single quoted'}.
|
||||||
|
* <li>Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
|
||||||
|
* <li>Octal integers prefixed with {@code 0}.
|
||||||
|
* <li>Array elements separated by {@code ;}.
|
||||||
|
* <li>Unnecessary array separators. These are interpreted as if null was the omitted
|
||||||
|
* value.
|
||||||
|
* <li>Key-value pairs separated by {@code =} or {@code =>}.
|
||||||
|
* <li>Key-value pairs separated by {@code ;}.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 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 <i>Effective Java</i> 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 '\'', '"':
|
||||||
|
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', ' ', '\n', '\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
|
||||||
|
* https://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 '\'', '"', '\\':
|
||||||
|
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 ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... 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 ';', ',':
|
||||||
|
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 ',', ';':
|
||||||
|
/* 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 ',', ';':
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -33,10 +33,10 @@ import org.apache.hc.core5.http.HttpHeaders;
|
||||||
import org.apache.hc.core5.http.HttpHost;
|
import org.apache.hc.core5.http.HttpHost;
|
||||||
import org.apache.hc.core5.http.message.BasicHeader;
|
import org.apache.hc.core5.http.message.BasicHeader;
|
||||||
import org.apache.hc.core5.http.message.StatusLine;
|
import org.apache.hc.core5.http.message.StatusLine;
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.boot.cli.json.JSONException;
|
||||||
|
import org.springframework.boot.cli.json.JSONObject;
|
||||||
import org.springframework.boot.cli.util.Log;
|
import org.springframework.boot.cli.util.Log;
|
||||||
import org.springframework.util.FileCopyUtils;
|
import org.springframework.util.FileCopyUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
|
@ -22,11 +22,11 @@ import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.boot.cli.json.JSONArray;
|
||||||
|
import org.springframework.boot.cli.json.JSONException;
|
||||||
|
import org.springframework.boot.cli.json.JSONObject;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,10 +20,10 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.cli.json.JSONException;
|
||||||
|
import org.springframework.boot.cli.json.JSONObject;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
|
@ -23,10 +23,10 @@ import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.cli.json.JSONException;
|
||||||
|
import org.springframework.boot.cli.json.JSONObject;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
|
@ -36,6 +36,8 @@
|
||||||
<suppress files="Ansi.*\.java" checks="JavadocStyle" />
|
<suppress files="Ansi.*\.java" checks="JavadocStyle" />
|
||||||
<suppress files="LogLevel\.java" checks="JavadocVariable" />
|
<suppress files="LogLevel\.java" checks="JavadocVariable" />
|
||||||
<suppress files="HelpMojo\.java" checks=".*"/>
|
<suppress files="HelpMojo\.java" checks=".*"/>
|
||||||
|
<suppress files="[\\/]org.springframework.boot.cli.json[\\/].*\.java$" checks=".*" />
|
||||||
|
<suppress files="[\\/]org.springframework.boot.configurationmetadata.json[\\/].*\.java$" checks=".*" />
|
||||||
<suppress files="[\\/]org.springframework.boot.configurationprocessor.json[\\/].*\.java$" checks=".*" />
|
<suppress files="[\\/]org.springframework.boot.configurationprocessor.json[\\/].*\.java$" checks=".*" />
|
||||||
<suppress files="TripType\.java" checks="JavadocVariable" />
|
<suppress files="TripType\.java" checks="JavadocVariable" />
|
||||||
<suppress files="Rating\.java" checks="JavadocVariable" />
|
<suppress files="Rating\.java" checks="JavadocVariable" />
|
||||||
|
|
|
@ -21,9 +21,15 @@ plugins {
|
||||||
|
|
||||||
description = "Spring Boot Configuration Metadata"
|
description = "Spring Boot Configuration Metadata"
|
||||||
|
|
||||||
dependencies {
|
sourceSets {
|
||||||
implementation("com.vaadin.external.google:android-json")
|
main {
|
||||||
|
java {
|
||||||
|
srcDir file("src/json-shade/java")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
testImplementation("org.assertj:assertj-core")
|
testImplementation("org.assertj:assertj-core")
|
||||||
testImplementation("org.springframework:spring-core")
|
testImplementation("org.springframework:spring-core")
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
## Shaded JSON
|
||||||
|
|
||||||
|
This source was originally taken from `com.vaadin.external.google:android-json` which
|
||||||
|
provides a clean room re-implementation of the `org.json` APIs and does not include the
|
||||||
|
"Do not use for evil" clause.
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
class JSON {
|
||||||
|
|
||||||
|
static double checkDouble(double d) throws JSONException {
|
||||||
|
if (Double.isInfinite(d) || Double.isNaN(d)) {
|
||||||
|
throw new JSONException("Forbidden numeric value: " + d);
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Boolean toBoolean(Object value) {
|
||||||
|
if (value instanceof Boolean) {
|
||||||
|
return (Boolean) value;
|
||||||
|
}
|
||||||
|
if (value instanceof String stringValue) {
|
||||||
|
if ("true".equalsIgnoreCase(stringValue)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ("false".equalsIgnoreCase(stringValue)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Double toDouble(Object value) {
|
||||||
|
if (value instanceof Double) {
|
||||||
|
return (Double) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).doubleValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return Double.valueOf((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Integer toInteger(Object value) {
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).intValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return (int) Double.parseDouble((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Long toLong(Object value) {
|
||||||
|
if (value instanceof Long) {
|
||||||
|
return (Long) value;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
return ((Number) value).longValue();
|
||||||
|
}
|
||||||
|
if (value instanceof String) {
|
||||||
|
try {
|
||||||
|
return (long) Double.parseDouble((String) value);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String toString(Object value) {
|
||||||
|
if (value instanceof String) {
|
||||||
|
return (String) value;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONException typeMismatch(Object indexOrName, Object actual, String requiredType)
|
||||||
|
throws JSONException {
|
||||||
|
if (actual == null) {
|
||||||
|
throw new JSONException("Value at " + indexOrName + " is null.");
|
||||||
|
}
|
||||||
|
throw new JSONException("Value " + actual + " at " + indexOrName + " of type " + actual.getClass().getName()
|
||||||
|
+ " cannot be converted to " + requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSONException typeMismatch(Object actual, String requiredType) throws JSONException {
|
||||||
|
if (actual == null) {
|
||||||
|
throw new JSONException("Value is null.");
|
||||||
|
}
|
||||||
|
throw new JSONException("Value " + actual + " of type " + actual.getClass().getName()
|
||||||
|
+ " cannot be converted to " + requiredType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,669 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dense indexed sequence of values. Values may be any mix of {@link JSONObject
|
||||||
|
* JSONObjects}, other {@link JSONArray JSONArrays}, Strings, Booleans, Integers, Longs,
|
||||||
|
* Doubles, {@code null} or {@link JSONObject#NULL}. Values may not be
|
||||||
|
* {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not
|
||||||
|
* listed here.
|
||||||
|
* <p>
|
||||||
|
* {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors
|
||||||
|
* as {@link JSONObject}. See that class' documentation for details.
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> this class represents null in two incompatible ways: the
|
||||||
|
* standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}.
|
||||||
|
* In particular, {@code get} fails if the requested index holds the null reference, but
|
||||||
|
* succeeds if it holds {@code JSONObject.NULL}.
|
||||||
|
* <p>
|
||||||
|
* 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
|
||||||
|
* overridable methods is not specified. See <i>Effective Java</i> Item 17, "Design and
|
||||||
|
* Document or inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONArray {
|
||||||
|
|
||||||
|
private final List<Object> values;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code JSONArray} with no values.
|
||||||
|
*/
|
||||||
|
public JSONArray() {
|
||||||
|
this.values = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} by copying all values from the given collection.
|
||||||
|
* @param copyFrom a collection whose values are of supported types. Unsupported
|
||||||
|
* values are not permitted and will yield an array in an inconsistent state.
|
||||||
|
*/
|
||||||
|
/* Accept a raw type for API compatibility */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public JSONArray(Collection copyFrom) {
|
||||||
|
this();
|
||||||
|
if (copyFrom != null) {
|
||||||
|
for (Iterator it = copyFrom.iterator(); it.hasNext();) {
|
||||||
|
put(JSONObject.wrap(it.next()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the next array in the tokener.
|
||||||
|
* @param readFrom a tokener whose nextValue() method will yield a {@code JSONArray}.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code JSONArray}.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray(JSONTokener readFrom) throws JSONException {
|
||||||
|
/*
|
||||||
|
* Getting the parser to populate this could get tricky. Instead, just parse to
|
||||||
|
* temporary JSONArray and then steal the data from that.
|
||||||
|
*/
|
||||||
|
Object object = readFrom.nextValue();
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
this.values = ((JSONArray) object).values;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the JSON string.
|
||||||
|
* @param json a JSON-encoded string containing an array.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray(String json) throws JSONException {
|
||||||
|
this(new JSONTokener(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONArray} with values from the given primitive array.
|
||||||
|
* @param array a primitive array
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray(Object array) throws JSONException {
|
||||||
|
if (!array.getClass().isArray()) {
|
||||||
|
throw new JSONException("Not a primitive array: " + array.getClass());
|
||||||
|
}
|
||||||
|
final int length = Array.getLength(array);
|
||||||
|
this.values = new ArrayList<>(length);
|
||||||
|
for (int i = 0; i < length; ++i) {
|
||||||
|
put(JSONObject.wrap(Array.get(array, i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of values in this array.
|
||||||
|
* @return the length of this array
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
return this.values.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(boolean value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(double value) throws JSONException {
|
||||||
|
this.values.add(JSON.checkDouble(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(int value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(long value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the end of this array.
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be
|
||||||
|
* {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}. Unsupported
|
||||||
|
* values are not permitted and will cause the array to be in an inconsistent state.
|
||||||
|
* @return this array.
|
||||||
|
*/
|
||||||
|
public JSONArray put(Object value) {
|
||||||
|
this.values.add(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, boolean value) throws JSONException {
|
||||||
|
return put(index, (Boolean) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, double value) throws JSONException {
|
||||||
|
return put(index, (Double) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, int value) throws JSONException {
|
||||||
|
return put(index, (Integer) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value the value
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, long value) throws JSONException {
|
||||||
|
return put(index, (Long) value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value at {@code index} to {@code value}, null padding this array to the
|
||||||
|
* required length if necessary. If a value already exists at {@code
|
||||||
|
* index}, it will be replaced.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link JSONObject#NULL}, or {@code null}. May not be
|
||||||
|
* {@link Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this array.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONArray put(int index, Object value) throws JSONException {
|
||||||
|
if (value instanceof Number) {
|
||||||
|
// deviate from the original by checking all Numbers, not just floats &
|
||||||
|
// doubles
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
while (this.values.size() <= index) {
|
||||||
|
this.values.add(null);
|
||||||
|
}
|
||||||
|
this.values.set(index, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this array has no value at {@code index}, or if its value is the
|
||||||
|
* {@code null} reference or {@link JSONObject#NULL}.
|
||||||
|
* @param index the index to set the value to
|
||||||
|
* @return true if this array has no value at {@code index}
|
||||||
|
*/
|
||||||
|
public boolean isNull(int index) {
|
||||||
|
Object value = opt(index);
|
||||||
|
return value == null || value == JSONObject.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index}.
|
||||||
|
* @throws JSONException if this array has no value at {@code index}, or if that value
|
||||||
|
* is the {@code null} reference. This method returns normally if the value is
|
||||||
|
* {@code JSONObject#NULL}.
|
||||||
|
*/
|
||||||
|
public Object get(int index) throws JSONException {
|
||||||
|
try {
|
||||||
|
Object value = this.values.get(index);
|
||||||
|
if (value == null) {
|
||||||
|
throw new JSONException("Value at " + index + " is null.");
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException e) {
|
||||||
|
throw new JSONException("Index " + index + " out of range [0.." + this.values.size() + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index}, or null if the array has no value at
|
||||||
|
* {@code index}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public Object opt(int index) {
|
||||||
|
if (index < 0 || index >= this.values.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.values.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and returns the value at {@code index}, or null if the array has no value
|
||||||
|
* at {@code index}.
|
||||||
|
* @param index the index of the value to remove
|
||||||
|
* @return the previous value at {@code index}
|
||||||
|
*/
|
||||||
|
public Object remove(int index) {
|
||||||
|
if (index < 0 || index >= this.values.size()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.values.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the value at {@code index}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a boolean.
|
||||||
|
*/
|
||||||
|
public boolean getBoolean(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "boolean");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean. Returns false otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code false}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(int index) {
|
||||||
|
return optBoolean(index, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a boolean or can be coerced
|
||||||
|
* to a boolean. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(int index, boolean fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a double.
|
||||||
|
*/
|
||||||
|
public double getDouble(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "double");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double. Returns {@code NaN} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code NaN}
|
||||||
|
*/
|
||||||
|
public double optDouble(int index) {
|
||||||
|
return optDouble(index, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a double or can be coerced
|
||||||
|
* to a double. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public double optDouble(int index, double fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to an int.
|
||||||
|
*/
|
||||||
|
public int getInt(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "int");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int. Returns 0 otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code 0}
|
||||||
|
*/
|
||||||
|
public int optInt(int index) {
|
||||||
|
return optInt(index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is an int or can be coerced to
|
||||||
|
* an int. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public int optInt(int index, int fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if the value at {@code index} doesn't exist or cannot be
|
||||||
|
* coerced to a long.
|
||||||
|
*/
|
||||||
|
public long getLong(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "long");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long. Returns 0 otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or {@code 0}
|
||||||
|
*/
|
||||||
|
public long optLong(int index) {
|
||||||
|
return optLong(index, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a long or can be coerced to
|
||||||
|
* a long. Returns {@code fallback} otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public long optLong(int index, long fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value}
|
||||||
|
* @throws JSONException if no such value exists.
|
||||||
|
*/
|
||||||
|
public String getString(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(index, object, "String");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary. Returns
|
||||||
|
* the empty string if no such value exists.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the {@code value} or an empty string
|
||||||
|
*/
|
||||||
|
public String optString(int index) {
|
||||||
|
return optString(index, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists, coercing it if necessary. Returns
|
||||||
|
* {@code fallback} if no such value exists.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @param fallback the fallback value
|
||||||
|
* @return the value at {@code index} of {@code fallback}
|
||||||
|
*/
|
||||||
|
public String optString(int index, String fallback) {
|
||||||
|
Object object = opt(index);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the array at {@code index}
|
||||||
|
* @throws JSONException if the value doesn't exist or is not a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray getJSONArray(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
return (JSONArray) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(index, object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONArray}. Returns null otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the array at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONArray optJSONArray(int index) {
|
||||||
|
Object object = opt(index);
|
||||||
|
return object instanceof JSONArray ? (JSONArray) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the object at {@code index}
|
||||||
|
* @throws JSONException if the value doesn't exist or is not a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject getJSONObject(int index) throws JSONException {
|
||||||
|
Object object = get(index);
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
return (JSONObject) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(index, object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value at {@code index} if it exists and is a {@code
|
||||||
|
* JSONObject}. Returns null otherwise.
|
||||||
|
* @param index the index to get the value from
|
||||||
|
* @return the object at {@code index} or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONObject optJSONObject(int index) {
|
||||||
|
Object object = opt(index);
|
||||||
|
return object instanceof JSONObject ? (JSONObject) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new object whose values are the values in this array, and whose names are
|
||||||
|
* the values in {@code names}. Names and values are paired up by index from 0 through
|
||||||
|
* to the shorter array's length. Names that are not strings will be coerced to
|
||||||
|
* strings. This method returns null if either array is empty.
|
||||||
|
* @param names the property names
|
||||||
|
* @return a json object
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONObject toJSONObject(JSONArray names) throws JSONException {
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
int length = Math.min(names.length(), this.values.size());
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
String name = JSON.toString(names.opt(i));
|
||||||
|
result.put(name, opt(i));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new string by alternating this array's values with {@code
|
||||||
|
* separator}. This array's string values are quoted and have their special characters
|
||||||
|
* escaped. For example, the array containing the strings '12" pizza', 'taco' and
|
||||||
|
* 'soda' joined on '+' returns this: <pre>"12\" pizza"+"taco"+"soda"</pre>
|
||||||
|
* @param separator the separator to use
|
||||||
|
* @return the joined value
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public String join(String separator) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
stringer.open(JSONStringer.Scope.NULL, "");
|
||||||
|
for (int i = 0, size = this.values.size(); i < size; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
stringer.out.append(separator);
|
||||||
|
}
|
||||||
|
stringer.value(this.values.get(i));
|
||||||
|
}
|
||||||
|
stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
|
||||||
|
return stringer.out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this array as a compact JSON string, such as: <pre>[94043,90210]</pre>
|
||||||
|
* @return a compact JSON string representation of this array
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this array as a human-readable JSON string for debugging, such as: <pre>
|
||||||
|
* [
|
||||||
|
* 94043,
|
||||||
|
* 90210
|
||||||
|
* ]</pre>
|
||||||
|
* @param indentSpaces the number of spaces to indent for each level of nesting.
|
||||||
|
* @return a human-readable JSON string of this array
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public String toString(int indentSpaces) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer(indentSpaces);
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeTo(JSONStringer stringer) throws JSONException {
|
||||||
|
stringer.array();
|
||||||
|
for (Object value : this.values) {
|
||||||
|
stringer.value(value);
|
||||||
|
}
|
||||||
|
stringer.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof JSONArray && ((JSONArray) o).values.equals(this.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
// diverge from the original, which doesn't implement hashCode
|
||||||
|
return this.values.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown to indicate a problem with the JSON API. Such problems include:
|
||||||
|
* <ul>
|
||||||
|
* <li>Attempts to parse or construct malformed documents
|
||||||
|
* <li>Use of null as a name
|
||||||
|
* <li>Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* <li>Lookups using an out of range index or nonexistent name
|
||||||
|
* <li>Type mismatches on lookups
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* Although this is a checked exception, it is rarely recoverable. Most callers should
|
||||||
|
* simply wrap this exception in an unchecked exception and rethrow: <pre class="code">
|
||||||
|
* public JSONArray toJSONObject() {
|
||||||
|
* try {
|
||||||
|
* JSONObject result = new JSONObject();
|
||||||
|
* ...
|
||||||
|
* } catch (JSONException e) {
|
||||||
|
* throw new RuntimeException(e);
|
||||||
|
* }
|
||||||
|
* }</pre>
|
||||||
|
*/
|
||||||
|
public class JSONException extends Exception {
|
||||||
|
|
||||||
|
public JSONException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,836 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A modifiable set of name/value mappings. Names are unique, non-null strings. Values may
|
||||||
|
* be any mix of {@link JSONObject JSONObjects}, {@link JSONArray JSONArrays}, Strings,
|
||||||
|
* Booleans, Integers, Longs, Doubles or {@link #NULL}. Values may not be {@code null},
|
||||||
|
* {@link Double#isNaN() NaNs}, {@link Double#isInfinite() infinities}, or of any type not
|
||||||
|
* listed here.
|
||||||
|
* <p>
|
||||||
|
* This class can coerce values to another type when requested.
|
||||||
|
* <ul>
|
||||||
|
* <li>When the requested type is a boolean, strings will be coerced using a
|
||||||
|
* case-insensitive comparison to "true" and "false".
|
||||||
|
* <li>When the requested type is a double, other {@link Number} types will be coerced
|
||||||
|
* using {@link Number#doubleValue() doubleValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be.
|
||||||
|
* <li>When the requested type is an int, other {@link Number} types will be coerced using
|
||||||
|
* {@link Number#intValue() intValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be, and then cast to int.
|
||||||
|
* <li><a id="lossy">When the requested type is a long, other {@link Number} types will be
|
||||||
|
* coerced using {@link Number#longValue() longValue}. Strings that can be coerced using
|
||||||
|
* {@link Double#valueOf(String)} will be, and then cast to long. This two-step conversion
|
||||||
|
* is lossy for very large values. For example, the string "9223372036854775806" yields
|
||||||
|
* the long 9223372036854775807.</a>
|
||||||
|
* <li>When the requested type is a String, other non-null values will be coerced using
|
||||||
|
* {@link String#valueOf(Object)}. Although null cannot be coerced, the sentinel value
|
||||||
|
* {@link JSONObject#NULL} is coerced to the string "null".
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* This class can look up both mandatory and optional values:
|
||||||
|
* <ul>
|
||||||
|
* <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This fails with a
|
||||||
|
* {@code JSONException} if the requested name has no value or if the value cannot be
|
||||||
|
* coerced to the requested type.
|
||||||
|
* <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This returns a
|
||||||
|
* system- or user-supplied default if the requested name has no value or if the value
|
||||||
|
* cannot be coerced to the requested type.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> this class represents null in two incompatible ways: the
|
||||||
|
* standard Java {@code null} reference, and the sentinel value {@link JSONObject#NULL}.
|
||||||
|
* In particular, calling {@code put(name, null)} removes the named entry from the object
|
||||||
|
* but {@code put(name, JSONObject.NULL)} stores an entry whose value is
|
||||||
|
* {@code JSONObject.NULL}.
|
||||||
|
* <p>
|
||||||
|
* 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 <i>Effective Java</i> Item 17, "Design and
|
||||||
|
* Document or inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONObject {
|
||||||
|
|
||||||
|
private static final Double NEGATIVE_ZERO = -0d;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sentinel value used to explicitly define a name with no value. Unlike
|
||||||
|
* {@code null}, names with this value:
|
||||||
|
* <ul>
|
||||||
|
* <li>show up in the {@link #names} array
|
||||||
|
* <li>show up in the {@link #keys} iterator
|
||||||
|
* <li>return {@code true} for {@link #has(String)}
|
||||||
|
* <li>do not throw on {@link #get(String)}
|
||||||
|
* <li>are included in the encoded JSON string.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* This value violates the general contract of {@link Object#equals} by returning true
|
||||||
|
* when compared to {@code null}. Its {@link #toString} method returns "null".
|
||||||
|
*/
|
||||||
|
public static final Object NULL = new Object() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o == this || o == null; // API specifies this broken equals
|
||||||
|
// implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "null";
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Map<String, Object> nameValuePairs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@code JSONObject} with no name/value mappings.
|
||||||
|
*/
|
||||||
|
public JSONObject() {
|
||||||
|
this.nameValuePairs = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} by copying all name/value mappings from the given
|
||||||
|
* map.
|
||||||
|
* @param copyFrom a map whose keys are of type {@link String} and whose values are of
|
||||||
|
* supported types.
|
||||||
|
* @throws NullPointerException if any of the map's keys are null.
|
||||||
|
*/
|
||||||
|
/* (accept a raw type for API compatibility) */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public JSONObject(Map copyFrom) {
|
||||||
|
this();
|
||||||
|
Map<?, ?> contentsTyped = copyFrom;
|
||||||
|
for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
|
||||||
|
/*
|
||||||
|
* Deviate from the original by checking that keys are non-null and of the
|
||||||
|
* proper type. (We still defer validating the values).
|
||||||
|
*/
|
||||||
|
String key = (String) entry.getKey();
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("key == null");
|
||||||
|
}
|
||||||
|
this.nameValuePairs.put(key, wrap(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} with name/value mappings from the next object in
|
||||||
|
* the tokener.
|
||||||
|
* @param readFrom a tokener whose nextValue() method will yield a {@code JSONObject}.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject(JSONTokener readFrom) throws JSONException {
|
||||||
|
/*
|
||||||
|
* Getting the parser to populate this could get tricky. Instead, just parse to
|
||||||
|
* temporary JSONObject and then steal the data from that.
|
||||||
|
*/
|
||||||
|
Object object = readFrom.nextValue();
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
this.nameValuePairs = ((JSONObject) object).nameValuePairs;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} with name/value mappings from the JSON string.
|
||||||
|
* @param json a JSON-encoded string containing an object.
|
||||||
|
* @throws JSONException if the parse fails or doesn't yield a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject(String json) throws JSONException {
|
||||||
|
this(new JSONTokener(json));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@code JSONObject} by copying mappings for the listed names from the
|
||||||
|
* given object. Names that aren't present in {@code copyFrom} will be skipped.
|
||||||
|
* @param copyFrom the source
|
||||||
|
* @param names the property names
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject(JSONObject copyFrom, String[] names) throws JSONException {
|
||||||
|
this();
|
||||||
|
for (String name : names) {
|
||||||
|
Object value = copyFrom.opt(name);
|
||||||
|
if (value != null) {
|
||||||
|
this.nameValuePairs.put(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of name/value mappings in this object.
|
||||||
|
* @return the number of name/value mappings in this object
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
return this.nameValuePairs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, boolean value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, double value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), JSON.checkDouble(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, int value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, long value) throws JSONException {
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps {@code name} to {@code value}, clobbering any existing name/value mapping with
|
||||||
|
* the same name. If the value is {@code null}, any existing mapping for {@code name}
|
||||||
|
* is removed.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link #NULL}, or {@code null}. May not be {@link Double#isNaN()
|
||||||
|
* NaNs} or {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject put(String name, Object value) throws JSONException {
|
||||||
|
if (value == null) {
|
||||||
|
this.nameValuePairs.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (value instanceof Number) {
|
||||||
|
// deviate from the original by checking all Numbers, not just floats &
|
||||||
|
// doubles
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
this.nameValuePairs.put(checkName(name), value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equivalent to {@code put(name, value)} when both parameters are non-null; does
|
||||||
|
* nothing otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value the value of the property
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject putOpt(String name, Object value) throws JSONException {
|
||||||
|
if (name == null || value == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends {@code value} to the array already mapped to {@code name}. If this object
|
||||||
|
* has no mapping for {@code name}, this inserts a new mapping. If the mapping exists
|
||||||
|
* but its value is not an array, the existing and new values are inserted in order
|
||||||
|
* into a new array which is itself mapped to {@code name}. In aggregate, this allows
|
||||||
|
* values to be added to a mapping one at a time.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double, {@link #NULL} or null. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public JSONObject accumulate(String name, Object value) throws JSONException {
|
||||||
|
Object current = this.nameValuePairs.get(checkName(name));
|
||||||
|
if (current == null) {
|
||||||
|
return put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check in accumulate, since array.put(Object) doesn't do any checking
|
||||||
|
if (value instanceof Number) {
|
||||||
|
JSON.checkDouble(((Number) value).doubleValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current instanceof JSONArray array) {
|
||||||
|
array.put(value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
array.put(current);
|
||||||
|
array.put(value);
|
||||||
|
this.nameValuePairs.put(name, array);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String checkName(String name) throws JSONException {
|
||||||
|
if (name == null) {
|
||||||
|
throw new JSONException("Names must be non-null");
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the named mapping if it exists; does nothing otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value previously mapped by {@code name}, or null if there was no such
|
||||||
|
* mapping.
|
||||||
|
*/
|
||||||
|
public Object remove(String name) {
|
||||||
|
return this.nameValuePairs.remove(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this object has no mapping for {@code name} or if it has a mapping
|
||||||
|
* whose value is {@link #NULL}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return true if this object has no mapping for {@code name}
|
||||||
|
*/
|
||||||
|
public boolean isNull(String name) {
|
||||||
|
Object value = this.nameValuePairs.get(name);
|
||||||
|
return value == null || value == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this object has a mapping for {@code name}. The mapping may be
|
||||||
|
* {@link #NULL}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return true if this object has a mapping for {@code name}
|
||||||
|
*/
|
||||||
|
public boolean has(String name) {
|
||||||
|
return this.nameValuePairs.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if no such mapping exists.
|
||||||
|
*/
|
||||||
|
public Object get(String name) throws JSONException {
|
||||||
|
Object result = this.nameValuePairs.get(name);
|
||||||
|
if (result == null) {
|
||||||
|
throw new JSONException("No value for " + name);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name}, or null if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public Object opt(String name) {
|
||||||
|
return this.nameValuePairs.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a
|
||||||
|
* boolean.
|
||||||
|
*/
|
||||||
|
public boolean getBoolean(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "boolean");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean. Returns false otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(String name) {
|
||||||
|
return optBoolean(name, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a boolean or can be
|
||||||
|
* coerced to a boolean. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public boolean optBoolean(String name, boolean fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Boolean result = JSON.toBoolean(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a
|
||||||
|
* double.
|
||||||
|
*/
|
||||||
|
public double getDouble(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "double");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double. Returns {@code NaN} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code NaN}
|
||||||
|
*/
|
||||||
|
public double optDouble(String name) {
|
||||||
|
return optDouble(name, Double.NaN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a double or can be
|
||||||
|
* coerced to a double. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public double optDouble(String name, double fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Double result = JSON.toDouble(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to an int.
|
||||||
|
*/
|
||||||
|
public int getInt(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "int");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int. Returns 0 otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value of {@code 0}
|
||||||
|
*/
|
||||||
|
public int optInt(String name) {
|
||||||
|
return optInt(name, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is an int or can be
|
||||||
|
* coerced to an int. Returns {@code fallback} otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public int optInt(String name, int fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Integer result = JSON.toInteger(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Note that JSON represents numbers as doubles, so this is
|
||||||
|
* <a href="#lossy">lossy</a>; use strings to transfer numbers over JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or cannot be coerced to a long.
|
||||||
|
*/
|
||||||
|
public long getLong(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "long");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as
|
||||||
|
* doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via
|
||||||
|
* JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code 0L}
|
||||||
|
*/
|
||||||
|
public long optLong(String name) {
|
||||||
|
return optLong(name, 0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a long or can be
|
||||||
|
* coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents
|
||||||
|
* numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
|
||||||
|
* numbers over JSON.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public long optLong(String name, long fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
Long result = JSON.toLong(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if no such mapping exists.
|
||||||
|
*/
|
||||||
|
public String getString(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
if (result == null) {
|
||||||
|
throw JSON.typeMismatch(name, object, "String");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* Returns the empty string if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or an empty string
|
||||||
|
*/
|
||||||
|
public String optString(String name) {
|
||||||
|
return optString(name, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists, coercing it if necessary.
|
||||||
|
* Returns {@code fallback} if no such mapping exists.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @param fallback a fallback value
|
||||||
|
* @return the value or {@code fallback}
|
||||||
|
*/
|
||||||
|
public String optString(String name, String fallback) {
|
||||||
|
Object object = opt(name);
|
||||||
|
String result = JSON.toString(object);
|
||||||
|
return result != null ? result : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||||||
|
* JSONArray}.
|
||||||
|
*/
|
||||||
|
public JSONArray getJSONArray(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
if (object instanceof JSONArray) {
|
||||||
|
return (JSONArray) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(name, object, "JSONArray");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONArray}. Returns null otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONArray optJSONArray(String name) {
|
||||||
|
Object object = opt(name);
|
||||||
|
return object instanceof JSONArray ? (JSONArray) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value
|
||||||
|
* @throws JSONException if the mapping doesn't exist or is not a {@code
|
||||||
|
* JSONObject}.
|
||||||
|
*/
|
||||||
|
public JSONObject getJSONObject(String name) throws JSONException {
|
||||||
|
Object object = get(name);
|
||||||
|
if (object instanceof JSONObject) {
|
||||||
|
return (JSONObject) object;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw JSON.typeMismatch(name, object, "JSONObject");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value mapped by {@code name} if it exists and is a {@code
|
||||||
|
* JSONObject}. Returns null otherwise.
|
||||||
|
* @param name the name of the property
|
||||||
|
* @return the value or {@code null}
|
||||||
|
*/
|
||||||
|
public JSONObject optJSONObject(String name) {
|
||||||
|
Object object = opt(name);
|
||||||
|
return object instanceof JSONObject ? (JSONObject) object : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array with the values corresponding to {@code names}. The array contains
|
||||||
|
* null for names that aren't mapped. This method returns null if {@code names} is
|
||||||
|
* either null or empty.
|
||||||
|
* @param names the names of the properties
|
||||||
|
* @return the array
|
||||||
|
*/
|
||||||
|
public JSONArray toJSONArray(JSONArray names) {
|
||||||
|
JSONArray result = new JSONArray();
|
||||||
|
if (names == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int length = names.length();
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
String name = JSON.toString(names.opt(i));
|
||||||
|
result.put(opt(name));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an iterator of the {@code String} names in this object. The returned
|
||||||
|
* iterator supports {@link Iterator#remove() remove}, which will remove the
|
||||||
|
* corresponding mapping from this object. If this object is modified after the
|
||||||
|
* iterator is returned, the iterator's behavior is undefined. The order of the keys
|
||||||
|
* is undefined.
|
||||||
|
* @return the keys
|
||||||
|
*/
|
||||||
|
/* Return a raw type for API compatibility */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public Iterator keys() {
|
||||||
|
return this.nameValuePairs.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing the string names in this object. This method returns
|
||||||
|
* null if this object contains no mappings.
|
||||||
|
* @return the array
|
||||||
|
*/
|
||||||
|
public JSONArray names() {
|
||||||
|
return this.nameValuePairs.isEmpty() ? null : new JSONArray(new ArrayList<>(this.nameValuePairs.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this object as a compact JSON string, such as:
|
||||||
|
* <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
|
||||||
|
* @return a string representation of the object.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes this object as a human-readable JSON string for debugging, such as: <pre>
|
||||||
|
* {
|
||||||
|
* "query": "Pizza",
|
||||||
|
* "locations": [
|
||||||
|
* 94043,
|
||||||
|
* 90210
|
||||||
|
* ]
|
||||||
|
* }</pre>
|
||||||
|
* @param indentSpaces the number of spaces to indent for each level of nesting.
|
||||||
|
* @return a string representation of the object.
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public String toString(int indentSpaces) throws JSONException {
|
||||||
|
JSONStringer stringer = new JSONStringer(indentSpaces);
|
||||||
|
writeTo(stringer);
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeTo(JSONStringer stringer) throws JSONException {
|
||||||
|
stringer.object();
|
||||||
|
for (Map.Entry<String, Object> entry : this.nameValuePairs.entrySet()) {
|
||||||
|
stringer.key(entry.getKey()).value(entry.getValue());
|
||||||
|
}
|
||||||
|
stringer.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the number as a JSON string.
|
||||||
|
* @param number a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return the encoded value
|
||||||
|
* @throws JSONException if an error occurs
|
||||||
|
*/
|
||||||
|
public static String numberToString(Number number) throws JSONException {
|
||||||
|
if (number == null) {
|
||||||
|
throw new JSONException("Number must be non-null");
|
||||||
|
}
|
||||||
|
|
||||||
|
double doubleValue = number.doubleValue();
|
||||||
|
JSON.checkDouble(doubleValue);
|
||||||
|
|
||||||
|
// the original returns "-0" instead of "-0.0" for negative zero
|
||||||
|
if (number.equals(NEGATIVE_ZERO)) {
|
||||||
|
return "-0";
|
||||||
|
}
|
||||||
|
|
||||||
|
long longValue = number.longValue();
|
||||||
|
if (doubleValue == longValue) {
|
||||||
|
return Long.toString(longValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return number.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code data} as a JSON string. This applies quotes and any necessary
|
||||||
|
* character escaping.
|
||||||
|
* @param data the string to encode. Null will be interpreted as an empty string.
|
||||||
|
* @return the quoted value
|
||||||
|
*/
|
||||||
|
public static String quote(String data) {
|
||||||
|
if (data == null) {
|
||||||
|
return "\"\"";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONStringer stringer = new JSONStringer();
|
||||||
|
stringer.open(JSONStringer.Scope.NULL, "");
|
||||||
|
stringer.value(data);
|
||||||
|
stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
|
||||||
|
return stringer.toString();
|
||||||
|
}
|
||||||
|
catch (JSONException e) {
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps the given object if necessary.
|
||||||
|
* <p>
|
||||||
|
* If the object is null or, returns {@link #NULL}. If the object is a
|
||||||
|
* {@code JSONArray} or {@code JSONObject}, no wrapping is necessary. If the object is
|
||||||
|
* {@code NULL}, no wrapping is necessary. If the object is an array or
|
||||||
|
* {@code Collection}, returns an equivalent {@code JSONArray}. If the object is a
|
||||||
|
* {@code Map}, returns an equivalent {@code JSONObject}. If the object is a primitive
|
||||||
|
* wrapper type or {@code String}, returns the object. Otherwise if the object is from
|
||||||
|
* a {@code java} package, returns the result of {@code toString}. If wrapping fails,
|
||||||
|
* returns null.
|
||||||
|
* @param o the object to wrap
|
||||||
|
* @return the wrapped object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static Object wrap(Object o) {
|
||||||
|
if (o == null) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (o instanceof JSONArray || o instanceof JSONObject) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
if (o.equals(NULL)) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (o instanceof Collection) {
|
||||||
|
return new JSONArray((Collection) o);
|
||||||
|
}
|
||||||
|
else if (o.getClass().isArray()) {
|
||||||
|
return new JSONArray(o);
|
||||||
|
}
|
||||||
|
if (o instanceof Map) {
|
||||||
|
return new JSONObject((Map) o);
|
||||||
|
}
|
||||||
|
if (o instanceof Boolean || o instanceof Byte || o instanceof Character || o instanceof Double
|
||||||
|
|| o instanceof Float || o instanceof Integer || o instanceof Long || o instanceof Short
|
||||||
|
|| o instanceof String) {
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
if (o.getClass().getPackage().getName().startsWith("java.")) {
|
||||||
|
return o.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,429 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most application
|
||||||
|
* developers should use those methods directly and disregard this API. For example:<pre>
|
||||||
|
* JSONObject object = ...
|
||||||
|
* String json = object.toString();</pre>
|
||||||
|
* <p>
|
||||||
|
* Stringers only encode well-formed JSON strings. In particular:
|
||||||
|
* <ul>
|
||||||
|
* <li>The stringer must have exactly one top-level array or object.
|
||||||
|
* <li>Lexical scopes must be balanced: every call to {@link #array} must have a matching
|
||||||
|
* call to {@link #endArray} and every call to {@link #object} must have a matching call
|
||||||
|
* to {@link #endObject}.
|
||||||
|
* <li>Arrays may not contain keys (property names).
|
||||||
|
* <li>Objects must alternate keys (property names) and values.
|
||||||
|
* <li>Values are inserted with either literal {@link #value(Object) value} calls, or by
|
||||||
|
* nesting arrays or objects.
|
||||||
|
* </ul>
|
||||||
|
* Calls that would result in a malformed JSON string will fail with a
|
||||||
|
* {@link JSONException}.
|
||||||
|
* <p>
|
||||||
|
* This class provides no facility for pretty-printing (ie. indenting) output. To encode
|
||||||
|
* indented output, use {@link JSONObject#toString(int)} or
|
||||||
|
* {@link JSONArray#toString(int)}.
|
||||||
|
* <p>
|
||||||
|
* Some implementations of the API support at most 20 levels of nesting. Attempts to
|
||||||
|
* create more than 20 levels of nesting may fail with a {@link JSONException}.
|
||||||
|
* <p>
|
||||||
|
* Each stringer may be used to encode a single top level value. 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 <i>Effective Java</i> Item 17, "Design and Document or
|
||||||
|
* inheritance or else prohibit it" for further information.
|
||||||
|
*/
|
||||||
|
public class JSONStringer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The output data, containing at most one top-level array or object.
|
||||||
|
*/
|
||||||
|
final StringBuilder out = new StringBuilder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexical scoping elements within this stringer, necessary to insert the appropriate
|
||||||
|
* separator characters (i.e. commas and colons) and to detect nesting errors.
|
||||||
|
*/
|
||||||
|
enum Scope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array with no elements requires no separators or newlines before it is
|
||||||
|
* closed.
|
||||||
|
*/
|
||||||
|
EMPTY_ARRAY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array with at least one value requires a comma and newline before the next
|
||||||
|
* element.
|
||||||
|
*/
|
||||||
|
NONEMPTY_ARRAY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with no keys or values requires no separators or newlines before it
|
||||||
|
* is closed.
|
||||||
|
*/
|
||||||
|
EMPTY_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object whose most recent element is a key. The next element must be a value.
|
||||||
|
*/
|
||||||
|
DANGLING_KEY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object with at least one name/value pair requires a comma and newline before
|
||||||
|
* the next element.
|
||||||
|
*/
|
||||||
|
NONEMPTY_OBJECT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A special bracketless array needed by JSONStringer.join() and
|
||||||
|
* JSONObject.quote() only. Not used for JSON encoding.
|
||||||
|
*/
|
||||||
|
NULL
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlike the original implementation, this stack isn't limited to 20 levels of
|
||||||
|
* nesting.
|
||||||
|
*/
|
||||||
|
private final List<Scope> stack = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A string containing a full set of spaces for a single level of indentation, or null
|
||||||
|
* for no pretty printing.
|
||||||
|
*/
|
||||||
|
private final String indent;
|
||||||
|
|
||||||
|
public JSONStringer() {
|
||||||
|
this.indent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONStringer(int indentSpaces) {
|
||||||
|
char[] indentChars = new char[indentSpaces];
|
||||||
|
Arrays.fill(indentChars, ' ');
|
||||||
|
this.indent = new String(indentChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new array. Each call to this method must be paired with a call to
|
||||||
|
* {@link #endArray}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer array() throws JSONException {
|
||||||
|
return open(Scope.EMPTY_ARRAY, "[");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current array.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer endArray() throws JSONException {
|
||||||
|
return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins encoding a new object. Each call to this method must be paired with a call
|
||||||
|
* to {@link #endObject}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer object() throws JSONException {
|
||||||
|
return open(Scope.EMPTY_OBJECT, "{");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends encoding the current object.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer endObject() throws JSONException {
|
||||||
|
return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enters a new scope by appending any necessary whitespace and the given bracket.
|
||||||
|
* @param empty any necessary whitespace
|
||||||
|
* @param openBracket the open bracket
|
||||||
|
* @return this object
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
JSONStringer open(Scope empty, String openBracket) throws JSONException {
|
||||||
|
if (this.stack.isEmpty() && !this.out.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem: multiple top-level roots");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.stack.add(empty);
|
||||||
|
this.out.append(openBracket);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the current scope by appending any necessary whitespace and the given
|
||||||
|
* bracket.
|
||||||
|
* @param empty any necessary whitespace
|
||||||
|
* @param nonempty the current scope
|
||||||
|
* @param closeBracket the close bracket
|
||||||
|
* @return the JSON stringer
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
|
||||||
|
Scope context = peek();
|
||||||
|
if (context != nonempty && context != empty) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stack.remove(this.stack.size() - 1);
|
||||||
|
if (context == nonempty) {
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
this.out.append(closeBracket);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value on the top of the stack.
|
||||||
|
* @return the scope
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private Scope peek() throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
return this.stack.get(this.stack.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the value on the top of the stack with the given value.
|
||||||
|
* @param topOfStack the scope at the top of the stack
|
||||||
|
*/
|
||||||
|
private void replaceTop(Scope topOfStack) {
|
||||||
|
this.stack.set(this.stack.size() - 1, topOfStack);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value}.
|
||||||
|
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer,
|
||||||
|
* Long, Double or null. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(Object value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value instanceof JSONArray) {
|
||||||
|
((JSONArray) value).writeTo(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (value instanceof JSONObject) {
|
||||||
|
((JSONObject) value).writeTo(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeValue();
|
||||||
|
|
||||||
|
if (value == null || value instanceof Boolean || value == JSONObject.NULL) {
|
||||||
|
this.out.append(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (value instanceof Number) {
|
||||||
|
this.out.append(JSONObject.numberToString((Number) value));
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string(value.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value the value to encode
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(boolean value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
|
||||||
|
* {@link Double#isInfinite() infinities}.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(double value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(JSONObject.numberToString(value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes {@code value} to this stringer.
|
||||||
|
* @param value the value to encode
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer value(long value) throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
beforeValue();
|
||||||
|
this.out.append(value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void string(String value) {
|
||||||
|
this.out.append("\"");
|
||||||
|
for (int i = 0, length = value.length(); i < length; i++) {
|
||||||
|
char c = value.charAt(i);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From RFC 4627, "All Unicode characters may be placed within the quotation
|
||||||
|
* marks except for the characters that must be escaped: quotation mark,
|
||||||
|
* reverse solidus, and the control characters (U+0000 through U+001F)."
|
||||||
|
*/
|
||||||
|
switch (c) {
|
||||||
|
case '"', '\\', '/' -> this.out.append('\\').append(c);
|
||||||
|
case '\t' -> this.out.append("\\t");
|
||||||
|
case '\b' -> this.out.append("\\b");
|
||||||
|
case '\n' -> this.out.append("\\n");
|
||||||
|
case '\r' -> this.out.append("\\r");
|
||||||
|
case '\f' -> this.out.append("\\f");
|
||||||
|
default -> {
|
||||||
|
if (c <= 0x1F) {
|
||||||
|
this.out.append(String.format("\\u%04x", (int) c));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
this.out.append("\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void newline() {
|
||||||
|
if (this.indent == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.out.append("\n");
|
||||||
|
this.out.append(this.indent.repeat(this.stack.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the key (property name) to this stringer.
|
||||||
|
* @param name the name of the forthcoming value. May not be null.
|
||||||
|
* @return this stringer.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
public JSONStringer key(String name) throws JSONException {
|
||||||
|
if (name == null) {
|
||||||
|
throw new JSONException("Names must be non-null");
|
||||||
|
}
|
||||||
|
beforeKey();
|
||||||
|
string(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts any necessary separators and whitespace before a name. Also adjusts the
|
||||||
|
* stack to expect the key's value.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private void beforeKey() throws JSONException {
|
||||||
|
Scope context = peek();
|
||||||
|
if (context == Scope.NONEMPTY_OBJECT) { // first in object
|
||||||
|
this.out.append(',');
|
||||||
|
}
|
||||||
|
else if (context != Scope.EMPTY_OBJECT) { // not in an object!
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
newline();
|
||||||
|
replaceTop(Scope.DANGLING_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts any necessary separators and whitespace before a literal value, inline
|
||||||
|
* array, or inline object. Also adjusts the stack to expect either a closing bracket
|
||||||
|
* or another element.
|
||||||
|
* @throws JSONException if processing of json failed
|
||||||
|
*/
|
||||||
|
private void beforeValue() throws JSONException {
|
||||||
|
if (this.stack.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope context = peek();
|
||||||
|
if (context == Scope.EMPTY_ARRAY) { // first in array
|
||||||
|
replaceTop(Scope.NONEMPTY_ARRAY);
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
else if (context == Scope.NONEMPTY_ARRAY) { // another in array
|
||||||
|
this.out.append(',');
|
||||||
|
newline();
|
||||||
|
}
|
||||||
|
else if (context == Scope.DANGLING_KEY) { // value for key
|
||||||
|
this.out.append(this.indent == null ? ":" : ": ");
|
||||||
|
replaceTop(Scope.NONEMPTY_OBJECT);
|
||||||
|
}
|
||||||
|
else if (context != Scope.NULL) {
|
||||||
|
throw new JSONException("Nesting problem");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoded JSON string.
|
||||||
|
* <p>
|
||||||
|
* If invoked with unterminated arrays or unclosed objects, this method's return value
|
||||||
|
* is undefined.
|
||||||
|
* <p>
|
||||||
|
* <strong>Warning:</strong> although it contradicts the general contract of
|
||||||
|
* {@link Object#toString}, this method returns null if the stringer contains no data.
|
||||||
|
* @return the encoded JSON string.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.out.isEmpty() ? null : this.out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,555 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-present the original author or authors.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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.configurationmetadata.json;
|
||||||
|
|
||||||
|
// Note: this class was written without inspecting the non-free org.json source code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a JSON (<a href="https://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) 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:
|
||||||
|
* <pre>
|
||||||
|
* String json = "{"
|
||||||
|
* + " \"query\": \"Pizza\", "
|
||||||
|
* + " \"locations\": [ 94043, 90210 ] "
|
||||||
|
* + "}";
|
||||||
|
*
|
||||||
|
* JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
|
||||||
|
* String query = object.getString("query");
|
||||||
|
* JSONArray locations = object.getJSONArray("locations");</pre>
|
||||||
|
* <p>
|
||||||
|
* 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 the
|
||||||
|
* following syntax errors will be ignored:
|
||||||
|
* <ul>
|
||||||
|
* <li>End of line comments starting with {@code //} or {@code #} and ending with a
|
||||||
|
* newline character.
|
||||||
|
* <li>C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such
|
||||||
|
* comments may not be nested.
|
||||||
|
* <li>Strings that are unquoted or {@code 'single quoted'}.
|
||||||
|
* <li>Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
|
||||||
|
* <li>Octal integers prefixed with {@code 0}.
|
||||||
|
* <li>Array elements separated by {@code ;}.
|
||||||
|
* <li>Unnecessary array separators. These are interpreted as if null was the omitted
|
||||||
|
* value.
|
||||||
|
* <li>Key-value pairs separated by {@code =} or {@code =>}.
|
||||||
|
* <li>Key-value pairs separated by {@code ;}.
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* 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 <i>Effective Java</i> 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 '\'', '"':
|
||||||
|
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', ' ', '\n', '\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
|
||||||
|
* https://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 '\'', '"', '\\':
|
||||||
|
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 ex) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ... 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 ';', ',':
|
||||||
|
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 ',', ';':
|
||||||
|
/* 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 ',', ';':
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,8 +25,8 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.springframework.boot.configurationmetadata.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.springframework.boot.configurationmetadata.json.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read standard json metadata format as {@link ConfigurationMetadataRepository}.
|
* Read standard json metadata format as {@link ConfigurationMetadataRepository}.
|
||||||
|
|
|
@ -21,9 +21,10 @@ import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.configurationmetadata.json.JSONException;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,6 @@ bom {
|
||||||
issueLabels = ["type: task"]
|
issueLabels = ["type: task"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
library("Android JSON", "0.0.20131108.vaadin1") {
|
|
||||||
group("com.vaadin.external.google") {
|
|
||||||
modules = [
|
|
||||||
"android-json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
library("API Guardian", "1.1.2") {
|
library("API Guardian", "1.1.2") {
|
||||||
group("org.apiguardian") {
|
group("org.apiguardian") {
|
||||||
modules = [
|
modules = [
|
||||||
|
|
Loading…
Reference in New Issue