diff --git a/cli/spring-boot-cli/build.gradle b/cli/spring-boot-cli/build.gradle
index de248614913..1667fbfeef5 100644
--- a/cli/spring-boot-cli/build.gradle
+++ b/cli/spring-boot-cli/build.gradle
@@ -26,6 +26,14 @@ plugins {
description = "Spring Boot CLI"
+sourceSets {
+ main {
+ java {
+ srcDir file("src/json-shade/java")
+ }
+ }
+}
+
configurations {
loader
testRepository
@@ -37,7 +45,6 @@ dependencies {
compileOnlyProject(project(":core:spring-boot"))
implementation(project(":loader:spring-boot-loader-tools"))
- implementation("com.vaadin.external.google:android-json")
implementation("jline:jline")
implementation("net.sf.jopt-simple:jopt-simple")
implementation("org.apache.httpcomponents.client5:httpclient5")
@@ -54,6 +61,10 @@ dependencies {
testImplementation(project(":test-support:spring-boot-test-support"))
}
+architectureCheck {
+ nullMarked = false
+}
+
tasks.register("fullJar", Jar) {
dependsOn configurations.loader
archiveClassifier = "full"
diff --git a/cli/spring-boot-cli/src/json-shade/README.adoc b/cli/spring-boot-cli/src/json-shade/README.adoc
new file mode 100644
index 00000000000..65480069457
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/README.adoc
@@ -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.
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSON.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSON.java
new file mode 100644
index 00000000000..8c276d88d3b
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSON.java
@@ -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);
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONArray.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONArray.java
new file mode 100644
index 00000000000..2185272dfa6
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONArray.java
@@ -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.
+ *
+ * {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors
+ * as {@link JSONObject}. See that class' documentation for details.
+ *
+ * Warning: 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}.
+ *
+ * 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 Effective Java Item 17, "Design and
+ * Document or inheritance or else prohibit it" for further information.
+ */
+public class JSONArray {
+
+ private final List 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: "12\" pizza"+"taco"+"soda"
+ * @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: [94043,90210]
+ * @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:
+ * [
+ * 94043,
+ * 90210
+ * ]
+ * @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();
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONException.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONException.java
new file mode 100644
index 00000000000..ebde92bce3d
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONException.java
@@ -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:
+ *
+ * Attempts to parse or construct malformed documents
+ * Use of null as a name
+ * Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * Lookups using an out of range index or nonexistent name
+ * Type mismatches on lookups
+ *
+ *
+ * Although this is a checked exception, it is rarely recoverable. Most callers should
+ * simply wrap this exception in an unchecked exception and rethrow:
+ * public JSONArray toJSONObject() {
+ * try {
+ * JSONObject result = new JSONObject();
+ * ...
+ * } catch (JSONException e) {
+ * throw new RuntimeException(e);
+ * }
+ * }
+ */
+public class JSONException extends Exception {
+
+ public JSONException(String s) {
+ super(s);
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONObject.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONObject.java
new file mode 100644
index 00000000000..a68468d32a0
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONObject.java
@@ -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.
+ *
+ * This class can coerce values to another type when requested.
+ *
+ *
+ * This class can look up both mandatory and optional values:
+ *
+ * Use getType () 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.
+ * Use optType () 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.
+ *
+ *
+ * Warning: 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}.
+ *
+ * Instances of this class are not thread safe. Although this class is nonfinal, it was
+ * not designed for inheritance and should not be subclassed. In particular, self-use by
+ * overrideable methods is not specified. See Effective Java Item 17, "Design and
+ * Document or inheritance or else prohibit it" for further information.
+ */
+public class 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:
+ *
+ * show up in the {@link #names} array
+ * show up in the {@link #keys} iterator
+ * return {@code true} for {@link #has(String)}
+ * do not throw on {@link #get(String)}
+ * are included in the encoded JSON string.
+ *
+ *
+ * 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 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
+ * lossy ; 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 lossy ; 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 lossy ; 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:
+ * {"query":"Pizza","locations":[94043,90210]}
+ * @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:
+ * {
+ * "query": "Pizza",
+ * "locations": [
+ * 94043,
+ * 90210
+ * ]
+ * }
+ * @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 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.
+ *
+ * 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;
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONStringer.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONStringer.java
new file mode 100644
index 00000000000..3a9a8736a68
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONStringer.java
@@ -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:
+ * JSONObject object = ...
+ * String json = object.toString();
+ *
+ * Stringers only encode well-formed JSON strings. In particular:
+ *
+ * The stringer must have exactly one top-level array or object.
+ * 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}.
+ * Arrays may not contain keys (property names).
+ * Objects must alternate keys (property names) and values.
+ * Values are inserted with either literal {@link #value(Object) value} calls, or by
+ * nesting arrays or objects.
+ *
+ * Calls that would result in a malformed JSON string will fail with a
+ * {@link JSONException}.
+ *
+ * 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)}.
+ *
+ * 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}.
+ *
+ * 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 Effective Java 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 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.
+ *
+ * If invoked with unterminated arrays or unclosed objects, this method's return value
+ * is undefined.
+ *
+ * Warning: 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();
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONTokener.java b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONTokener.java
new file mode 100644
index 00000000000..a8d3dfefc37
--- /dev/null
+++ b/cli/spring-boot-cli/src/json-shade/java/org/springframework/boot/cli/json/JSONTokener.java
@@ -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 (RFC 4627 ) encoded
+ * string into the corresponding object. Most clients of this class will use only need the
+ * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage:
+ *
+ * String json = "{"
+ * + " \"query\": \"Pizza\", "
+ * + " \"locations\": [ 94043, 90210 ] "
+ * + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ *
+ * For best interoperability and performance use JSON that complies with RFC 4627, such as
+ * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a
+ * successful parse does not indicate that the input string was valid JSON. All the
+ * following syntax errors will be ignored:
+ *
+ * End of line comments starting with {@code //} or {@code #} and ending with a
+ * newline character.
+ * C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such
+ * comments may not be nested.
+ * Strings that are unquoted or {@code 'single quoted'}.
+ * Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
+ * Octal integers prefixed with {@code 0}.
+ * Array elements separated by {@code ;}.
+ * Unnecessary array separators. These are interpreted as if null was the omitted
+ * value.
+ * Key-value pairs separated by {@code =} or {@code =>}.
+ * Key-value pairs separated by {@code ;}.
+ *
+ *
+ * Each tokener may be used to parse a single JSON string. Instances of this class are not
+ * thread safe. Although this class is nonfinal, it was not designed for inheritance and
+ * should not be subclassed. In particular, self-use by overrideable methods is not
+ * specified. See Effective Java Item 17, "Design and Document or inheritance or
+ * else prohibit it" for further information.
+ */
+public class JSONTokener {
+
+ /**
+ * The input JSON.
+ */
+ private final String in;
+
+ /**
+ * The index of the next character to be returned by {@link #next}. When the input is
+ * exhausted, this equals the input's length.
+ */
+ private int pos;
+
+ /**
+ * @param in JSON encoded string. Null is not permitted and will yield a tokener that
+ * throws {@code NullPointerExceptions} when methods are called.
+ */
+ public JSONTokener(String in) {
+ // consume an optional byte order mark (BOM) if it exists
+ if (in != null && in.startsWith("\ufeff")) {
+ in = in.substring(1);
+ }
+ this.in = in;
+ }
+
+ /**
+ * Returns the next value from the input.
+ * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
+ * Double or {@link JSONObject#NULL}.
+ * @throws JSONException if the input is malformed.
+ */
+ public Object nextValue() throws JSONException {
+ int c = nextCleanInternal();
+ switch (c) {
+ case -1:
+ throw syntaxError("End of input");
+
+ case '{':
+ return readObject();
+
+ case '[':
+ return readArray();
+
+ case '\'', '"':
+ 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;
+ }
+ }
+
+}
diff --git a/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java b/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java
index dab9556e9fd..63688d0ed76 100644
--- a/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java
+++ b/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrService.java
@@ -33,10 +33,10 @@ import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.StatusLine;
-import org.json.JSONException;
-import org.json.JSONObject;
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.util.FileCopyUtils;
import org.springframework.util.StringUtils;
diff --git a/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java b/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java
index 8e2bff69606..1577a94463d 100644
--- a/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java
+++ b/cli/spring-boot-cli/src/main/java/org/springframework/boot/cli/command/init/InitializrServiceMetadata.java
@@ -22,11 +22,11 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
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;
/**
diff --git a/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java b/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java
index d9a687de5fd..a13975c3e9b 100644
--- a/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java
+++ b/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/InitializrServiceMetadataTests.java
@@ -20,10 +20,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
-import org.json.JSONException;
-import org.json.JSONObject;
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.Resource;
import org.springframework.util.StreamUtils;
diff --git a/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java b/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java
index 349c9de9f10..4489fa4d4c2 100644
--- a/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java
+++ b/cli/spring-boot-cli/src/test/java/org/springframework/boot/cli/command/init/ProjectGenerationRequestTests.java
@@ -23,10 +23,10 @@ import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
-import org.json.JSONException;
-import org.json.JSONObject;
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.Resource;
import org.springframework.util.StreamUtils;
diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml
index 0b0e17a6c67..74d14e278a8 100644
--- a/config/checkstyle/checkstyle-suppressions.xml
+++ b/config/checkstyle/checkstyle-suppressions.xml
@@ -36,6 +36,8 @@
+
+
diff --git a/configuration-metadata/spring-boot-configuration-metadata/build.gradle b/configuration-metadata/spring-boot-configuration-metadata/build.gradle
index ac3aba5f755..ecc899004f0 100644
--- a/configuration-metadata/spring-boot-configuration-metadata/build.gradle
+++ b/configuration-metadata/spring-boot-configuration-metadata/build.gradle
@@ -21,9 +21,15 @@ plugins {
description = "Spring Boot Configuration Metadata"
-dependencies {
- implementation("com.vaadin.external.google:android-json")
+sourceSets {
+ main {
+ java {
+ srcDir file("src/json-shade/java")
+ }
+ }
+}
+dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.assertj:assertj-core")
testImplementation("org.springframework:spring-core")
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/README.adoc b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/README.adoc
new file mode 100644
index 00000000000..65480069457
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/README.adoc
@@ -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.
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSON.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSON.java
new file mode 100644
index 00000000000..6e261670ea7
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSON.java
@@ -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);
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONArray.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONArray.java
new file mode 100644
index 00000000000..d93e8e0ad01
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONArray.java
@@ -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.
+ *
+ * {@code JSONArray} has the same type coercion behavior and optional/mandatory accessors
+ * as {@link JSONObject}. See that class' documentation for details.
+ *
+ * Warning: 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}.
+ *
+ * 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 Effective Java Item 17, "Design and
+ * Document or inheritance or else prohibit it" for further information.
+ */
+public class JSONArray {
+
+ private final List 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: "12\" pizza"+"taco"+"soda"
+ * @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: [94043,90210]
+ * @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:
+ * [
+ * 94043,
+ * 90210
+ * ]
+ * @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();
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONException.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONException.java
new file mode 100644
index 00000000000..3b557b2b135
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONException.java
@@ -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:
+ *
+ * Attempts to parse or construct malformed documents
+ * Use of null as a name
+ * Use of numeric types not available to JSON, such as {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * Lookups using an out of range index or nonexistent name
+ * Type mismatches on lookups
+ *
+ *
+ * Although this is a checked exception, it is rarely recoverable. Most callers should
+ * simply wrap this exception in an unchecked exception and rethrow:
+ * public JSONArray toJSONObject() {
+ * try {
+ * JSONObject result = new JSONObject();
+ * ...
+ * } catch (JSONException e) {
+ * throw new RuntimeException(e);
+ * }
+ * }
+ */
+public class JSONException extends Exception {
+
+ public JSONException(String s) {
+ super(s);
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONObject.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONObject.java
new file mode 100644
index 00000000000..f03bbe6cb8e
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONObject.java
@@ -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.
+ *
+ * This class can coerce values to another type when requested.
+ *
+ *
+ * This class can look up both mandatory and optional values:
+ *
+ * Use getType () 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.
+ * Use optType () 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.
+ *
+ *
+ * Warning: 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}.
+ *
+ * Instances of this class are not thread safe. Although this class is nonfinal, it was
+ * not designed for inheritance and should not be subclassed. In particular, self-use by
+ * overrideable methods is not specified. See Effective Java Item 17, "Design and
+ * Document or inheritance or else prohibit it" for further information.
+ */
+public class 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:
+ *
+ * show up in the {@link #names} array
+ * show up in the {@link #keys} iterator
+ * return {@code true} for {@link #has(String)}
+ * do not throw on {@link #get(String)}
+ * are included in the encoded JSON string.
+ *
+ *
+ * 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 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
+ * lossy ; 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 lossy ; 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 lossy ; 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:
+ * {"query":"Pizza","locations":[94043,90210]}
+ * @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:
+ * {
+ * "query": "Pizza",
+ * "locations": [
+ * 94043,
+ * 90210
+ * ]
+ * }
+ * @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 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.
+ *
+ * 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;
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONStringer.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONStringer.java
new file mode 100644
index 00000000000..0f72049a133
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONStringer.java
@@ -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:
+ * JSONObject object = ...
+ * String json = object.toString();
+ *
+ * Stringers only encode well-formed JSON strings. In particular:
+ *
+ * The stringer must have exactly one top-level array or object.
+ * 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}.
+ * Arrays may not contain keys (property names).
+ * Objects must alternate keys (property names) and values.
+ * Values are inserted with either literal {@link #value(Object) value} calls, or by
+ * nesting arrays or objects.
+ *
+ * Calls that would result in a malformed JSON string will fail with a
+ * {@link JSONException}.
+ *
+ * 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)}.
+ *
+ * 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}.
+ *
+ * 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 Effective Java 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 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.
+ *
+ * If invoked with unterminated arrays or unclosed objects, this method's return value
+ * is undefined.
+ *
+ * Warning: 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();
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONTokener.java b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONTokener.java
new file mode 100644
index 00000000000..632a7df1be8
--- /dev/null
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/json-shade/java/org/springframework/boot/configurationmetadata/json/JSONTokener.java
@@ -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 (RFC 4627 ) encoded
+ * string into the corresponding object. Most clients of this class will use only need the
+ * {@link #JSONTokener(String) constructor} and {@link #nextValue} method. Example usage:
+ *
+ * String json = "{"
+ * + " \"query\": \"Pizza\", "
+ * + " \"locations\": [ 94043, 90210 ] "
+ * + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");
+ *
+ * For best interoperability and performance use JSON that complies with RFC 4627, such as
+ * that generated by {@link JSONStringer}. For legacy reasons this parser is lenient, so a
+ * successful parse does not indicate that the input string was valid JSON. All the
+ * following syntax errors will be ignored:
+ *
+ * End of line comments starting with {@code //} or {@code #} and ending with a
+ * newline character.
+ * C-style comments starting with {@code /*} and ending with {@code *}{@code /}. Such
+ * comments may not be nested.
+ * Strings that are unquoted or {@code 'single quoted'}.
+ * Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
+ * Octal integers prefixed with {@code 0}.
+ * Array elements separated by {@code ;}.
+ * Unnecessary array separators. These are interpreted as if null was the omitted
+ * value.
+ * Key-value pairs separated by {@code =} or {@code =>}.
+ * Key-value pairs separated by {@code ;}.
+ *
+ *
+ * Each tokener may be used to parse a single JSON string. Instances of this class are not
+ * thread safe. Although this class is nonfinal, it was not designed for inheritance and
+ * should not be subclassed. In particular, self-use by overrideable methods is not
+ * specified. See Effective Java Item 17, "Design and Document or inheritance or
+ * else prohibit it" for further information.
+ */
+public class JSONTokener {
+
+ /**
+ * The input JSON.
+ */
+ private final String in;
+
+ /**
+ * The index of the next character to be returned by {@link #next}. When the input is
+ * exhausted, this equals the input's length.
+ */
+ private int pos;
+
+ /**
+ * @param in JSON encoded string. Null is not permitted and will yield a tokener that
+ * throws {@code NullPointerExceptions} when methods are called.
+ */
+ public JSONTokener(String in) {
+ // consume an optional byte order mark (BOM) if it exists
+ if (in != null && in.startsWith("\ufeff")) {
+ in = in.substring(1);
+ }
+ this.in = in;
+ }
+
+ /**
+ * Returns the next value from the input.
+ * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean, Integer, Long,
+ * Double or {@link JSONObject#NULL}.
+ * @throws JSONException if the input is malformed.
+ */
+ public Object nextValue() throws JSONException {
+ int c = nextCleanInternal();
+ switch (c) {
+ case -1:
+ throw syntaxError("End of input");
+
+ case '{':
+ return readObject();
+
+ case '[':
+ return readArray();
+
+ case '\'', '"':
+ 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;
+ }
+ }
+
+}
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java b/configuration-metadata/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java
index d47f3563af8..e3820d7f8b4 100644
--- a/configuration-metadata/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java
@@ -25,8 +25,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import org.json.JSONArray;
-import org.json.JSONObject;
+import org.springframework.boot.configurationmetadata.json.JSONArray;
+import org.springframework.boot.configurationmetadata.json.JSONObject;
/**
* Read standard json metadata format as {@link ConfigurationMetadataRepository}.
diff --git a/configuration-metadata/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/JsonReaderTests.java b/configuration-metadata/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/JsonReaderTests.java
index 3be4cc7e7ab..5b8b66172b4 100644
--- a/configuration-metadata/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/JsonReaderTests.java
+++ b/configuration-metadata/spring-boot-configuration-metadata/src/test/java/org/springframework/boot/configurationmetadata/JsonReaderTests.java
@@ -21,9 +21,10 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
-import org.json.JSONException;
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.assertThatIllegalStateException;
diff --git a/platform/spring-boot-internal-dependencies/build.gradle b/platform/spring-boot-internal-dependencies/build.gradle
index 0ab9383dfbb..54b72acde41 100644
--- a/platform/spring-boot-internal-dependencies/build.gradle
+++ b/platform/spring-boot-internal-dependencies/build.gradle
@@ -27,13 +27,6 @@ bom {
issueLabels = ["type: task"]
}
}
- library("Android JSON", "0.0.20131108.vaadin1") {
- group("com.vaadin.external.google") {
- modules = [
- "android-json"
- ]
- }
- }
library("API Guardian", "1.1.2") {
group("org.apiguardian") {
modules = [