Merge branch '1.5.x'

This commit is contained in:
Stephane Nicoll 2018-01-08 16:04:19 +01:00
commit f2d3f51f3f
6 changed files with 2239 additions and 2061 deletions

View File

@ -17,100 +17,114 @@
package org.springframework.boot.configurationprocessor.json; package org.springframework.boot.configurationprocessor.json;
class JSON { class JSON {
/** /**
* Returns the input if it is a JSON-permissible value; throws otherwise. * Returns the input if it is a JSON-permissible value; throws otherwise.
*/ */
static double checkDouble(double d) throws JSONException { static double checkDouble(double d) throws JSONException {
if (Double.isInfinite(d) || Double.isNaN(d)) { if (Double.isInfinite(d) || Double.isNaN(d)) {
throw new JSONException("Forbidden numeric value: " + d); throw new JSONException("Forbidden numeric value: " + d);
} }
return d; return d;
} }
static Boolean toBoolean(Object value) { static Boolean toBoolean(Object value) {
if (value instanceof Boolean) { if (value instanceof Boolean) {
return (Boolean) value; return (Boolean) value;
} else if (value instanceof String) { }
String stringValue = (String) value; else if (value instanceof String) {
if ("true".equalsIgnoreCase(stringValue)) { String stringValue = (String) value;
return true; if ("true".equalsIgnoreCase(stringValue)) {
} else if ("false".equalsIgnoreCase(stringValue)) { return true;
return false; }
} else if ("false".equalsIgnoreCase(stringValue)) {
} return false;
return null; }
} }
return null;
}
static Double toDouble(Object value) { static Double toDouble(Object value) {
if (value instanceof Double) { if (value instanceof Double) {
return (Double) value; return (Double) value;
} else if (value instanceof Number) { }
return ((Number) value).doubleValue(); else if (value instanceof Number) {
} else if (value instanceof String) { return ((Number) value).doubleValue();
try { }
return Double.valueOf((String) value); else if (value instanceof String) {
} catch (NumberFormatException ignored) { try {
} return Double.valueOf((String) value);
} }
return null; catch (NumberFormatException ignored) {
} }
}
return null;
}
static Integer toInteger(Object value) { static Integer toInteger(Object value) {
if (value instanceof Integer) { if (value instanceof Integer) {
return (Integer) value; return (Integer) value;
} else if (value instanceof Number) { }
return ((Number) value).intValue(); else if (value instanceof Number) {
} else if (value instanceof String) { return ((Number) value).intValue();
try { }
return (int) Double.parseDouble((String) value); else if (value instanceof String) {
} catch (NumberFormatException ignored) { try {
} return (int) Double.parseDouble((String) value);
} }
return null; catch (NumberFormatException ignored) {
} }
}
return null;
}
static Long toLong(Object value) { static Long toLong(Object value) {
if (value instanceof Long) { if (value instanceof Long) {
return (Long) value; return (Long) value;
} else if (value instanceof Number) { }
return ((Number) value).longValue(); else if (value instanceof Number) {
} else if (value instanceof String) { return ((Number) value).longValue();
try { }
return (long) Double.parseDouble((String) value); else if (value instanceof String) {
} catch (NumberFormatException ignored) { try {
} return (long) Double.parseDouble((String) value);
} }
return null; catch (NumberFormatException ignored) {
} }
}
return null;
}
static String toString(Object value) { static String toString(Object value) {
if (value instanceof String) { if (value instanceof String) {
return (String) value; return (String) value;
} else if (value != null) { }
return String.valueOf(value); else if (value != null) {
} return String.valueOf(value);
return null; }
} return null;
}
public static JSONException typeMismatch(Object indexOrName, Object actual, public static JSONException typeMismatch(Object indexOrName, Object actual,
String requiredType) throws JSONException { String requiredType) throws JSONException {
if (actual == null) { if (actual == null) {
throw new JSONException("Value at " + indexOrName + " is null."); throw new JSONException("Value at " + indexOrName + " is null.");
} else { }
throw new JSONException("Value " + actual + " at " + indexOrName else {
+ " of type " + actual.getClass().getName() throw new JSONException("Value " + actual + " at " + indexOrName
+ " cannot be converted to " + requiredType); + " of type " + actual.getClass().getName()
} + " cannot be converted to " + requiredType);
} }
}
public static JSONException typeMismatch(Object actual, String requiredType) public static JSONException typeMismatch(Object actual, String requiredType)
throws JSONException { throws JSONException {
if (actual == null) { if (actual == null) {
throw new JSONException("Value is null."); throw new JSONException("Value is null.");
} else { }
throw new JSONException("Value " + actual else {
+ " of type " + actual.getClass().getName() throw new JSONException("Value " + actual
+ " cannot be converted to " + requiredType); + " of type " + actual.getClass().getName()
} + " cannot be converted to " + requiredType);
} }
}
} }

View File

@ -43,7 +43,7 @@ package org.springframework.boot.configurationprocessor.json;
*/ */
public class JSONException extends Exception { public class JSONException extends Exception {
public JSONException(String s) { public JSONException(String s) {
super(s); super(s);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2018 the original author or authors. * Copyright (C) 2010 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
// Note: this class was written without inspecting the non-free org.json source code. // Note: this class was written without inspecting the non-free org.json sourcecode.
/** /**
* Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
@ -60,373 +60,407 @@ import java.util.List;
*/ */
public class JSONStringer { public class JSONStringer {
/** The output data, containing at most one top-level array or object. */ /** The output data, containing at most one top-level array or object. */
final StringBuilder out = new StringBuilder(); final StringBuilder out = new StringBuilder();
/** /**
* Lexical scoping elements within this stringer, necessary to insert the * Lexical scoping elements within this stringer, necessary to insert the
* appropriate separator characters (ie. commas and colons) and to detect * appropriate separator characters (ie. commas and colons) and to detect
* nesting errors. * nesting errors.
*/ */
enum Scope { enum Scope {
/** /**
* An array with no elements requires no separators or newlines before * An array with no elements requires no separators or newlines before
* it is closed. * it is closed.
*/ */
EMPTY_ARRAY, EMPTY_ARRAY,
/** /**
* A array with at least one value requires a comma and newline before * A array with at least one value requires a comma and newline before
* the next element. * the next element.
*/ */
NONEMPTY_ARRAY, NONEMPTY_ARRAY,
/** /**
* An object with no keys or values requires no separators or newlines * An object with no keys or values requires no separators or newlines
* before it is closed. * before it is closed.
*/ */
EMPTY_OBJECT, EMPTY_OBJECT,
/** /**
* An object whose most recent element is a key. The next element must * An object whose most recent element is a key. The next element must
* be a value. * be a value.
*/ */
DANGLING_KEY, DANGLING_KEY,
/** /**
* An object with at least one name/value pair requires a comma and * An object with at least one name/value pair requires a comma and
* newline before the next element. * newline before the next element.
*/ */
NONEMPTY_OBJECT, NONEMPTY_OBJECT,
/** /**
* A special bracketless array needed by JSONStringer.join() and * A special bracketless array needed by JSONStringer.join() and
* JSONObject.quote() only. Not used for JSON encoding. * JSONObject.quote() only. Not used for JSON encoding.
*/ */
NULL NULL,
} }
/** /**
* Unlike the original implementation, this stack isn't limited to 20 * Unlike the original implementation, this stack isn't limited to 20
* levels of nesting. * levels of nesting.
*/ */
private final List<Scope> stack = new ArrayList<Scope>(); private final List<Scope> stack = new ArrayList<Scope>();
/** /**
* A string containing a full set of spaces for a single level of * A string containing a full set of spaces for a single level of
* indentation, or null for no pretty printing. * indentation, or null for no pretty printing.
*/ */
private final String indent; private final String indent;
public JSONStringer() { public JSONStringer() {
indent = null; this.indent = null;
} }
JSONStringer(int indentSpaces) { JSONStringer(int indentSpaces) {
char[] indentChars = new char[indentSpaces]; char[] indentChars = new char[indentSpaces];
Arrays.fill(indentChars, ' '); Arrays.fill(indentChars, ' ');
indent = new String(indentChars); this.indent = new String(indentChars);
} }
/** /**
* Begins encoding a new array. Each call to this method must be paired with * Begins encoding a new array. Each call to this method must be paired with
* a call to {@link #endArray}. * a call to {@link #endArray}.
* *
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer array() throws JSONException { */
return open(Scope.EMPTY_ARRAY, "["); public JSONStringer array() throws JSONException {
} return open(Scope.EMPTY_ARRAY, "[");
}
/** /**
* Ends encoding the current array. * Ends encoding the current array.
* *
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer endArray() throws JSONException { */
return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); 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 * Begins encoding a new object. Each call to this method must be paired
* with a call to {@link #endObject}. * with a call to {@link #endObject}.
* *
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer object() throws JSONException { */
return open(Scope.EMPTY_OBJECT, "{"); public JSONStringer object() throws JSONException {
} return open(Scope.EMPTY_OBJECT, "{");
}
/** /**
* Ends encoding the current object. * Ends encoding the current object.
* *
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer endObject() throws JSONException { */
return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); 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 * Enters a new scope by appending any necessary whitespace and the given
* bracket. * bracket.
*/ * @param empty any necessary whitespace
JSONStringer open(Scope empty, String openBracket) throws JSONException { * @param openBracket the open bracket
if (stack.isEmpty() && out.length() > 0) { * @return this object
throw new JSONException("Nesting problem: multiple top-level roots"); * @throws JSONException if processing of json failed
} */
beforeValue(); JSONStringer open(Scope empty, String openBracket) throws JSONException {
stack.add(empty); if (this.stack.isEmpty() && this.out.length() > 0) {
out.append(openBracket); throw new JSONException("Nesting problem: multiple top-level roots");
return this; }
} beforeValue();
this.stack.add(empty);
this.out.append(openBracket);
return this;
}
/** /**
* Closes the current scope by appending any necessary whitespace and the * Closes the current scope by appending any necessary whitespace and the
* given bracket. * given bracket.
*/ * @param empty any necessary whitespace
JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { * @param nonempty the current scope
Scope context = peek(); * @param closeBracket the close bracket
if (context != nonempty && context != empty) { * @throws JSONException if processing of json failed
throw new JSONException("Nesting problem"); */
} JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
Scope context = peek();
if (context != nonempty && context != empty) {
throw new JSONException("Nesting problem");
}
stack.remove(stack.size() - 1); this.stack.remove(this.stack.size() - 1);
if (context == nonempty) { if (context == nonempty) {
newline(); newline();
} }
out.append(closeBracket); this.out.append(closeBracket);
return this; return this;
} }
/** /**
* Returns the value on the top of the stack. * Returns the value on the top of the stack.
*/ * @return the scope
private Scope peek() throws JSONException { * @throws JSONException if processing of json failed
if (stack.isEmpty()) { */
throw new JSONException("Nesting problem"); private Scope peek() throws JSONException {
} if (this.stack.isEmpty()) {
return stack.get(stack.size() - 1); 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. * 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) { */
stack.set(stack.size() - 1, topOfStack); private void replaceTop(Scope topOfStack) {
} this.stack.set(this.stack.size() - 1, topOfStack);
}
/** /**
* Encodes {@code value}. * Encodes {@code value}.
* *
* @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean, * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
* Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs} * Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
* or {@link Double#isInfinite() infinities}. * or {@link Double#isInfinite() infinities}.
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer value(Object value) throws JSONException { */
if (stack.isEmpty()) { public JSONStringer value(Object value) throws JSONException {
throw new JSONException("Nesting problem"); if (this.stack.isEmpty()) {
} throw new JSONException("Nesting problem");
}
if (value instanceof JSONArray) { if (value instanceof JSONArray) {
((JSONArray) value).writeTo(this); ((JSONArray) value).writeTo(this);
return this; return this;
} else if (value instanceof JSONObject) { }
((JSONObject) value).writeTo(this); else if (value instanceof JSONObject) {
return this; ((JSONObject) value).writeTo(this);
} return this;
}
beforeValue(); beforeValue();
if (value == null if (value == null
|| value instanceof Boolean || value instanceof Boolean
|| value == JSONObject.NULL) { || value == JSONObject.NULL) {
out.append(value); this.out.append(value);
} else if (value instanceof Number) { }
out.append(JSONObject.numberToString((Number) value)); else if (value instanceof Number) {
this.out.append(JSONObject.numberToString((Number) value));
} else { }
string(value.toString()); else {
} string(value.toString());
}
return this; return this;
} }
/** /**
* Encodes {@code value} to this stringer. * Encodes {@code value} to this stringer.
* *
* @return this stringer. * @param value the value to encode
*/ * @return this stringer.
public JSONStringer value(boolean value) throws JSONException { * @throws JSONException if processing of json failed
if (stack.isEmpty()) { */
throw new JSONException("Nesting problem"); public JSONStringer value(boolean value) throws JSONException {
} if (this.stack.isEmpty()) {
beforeValue(); throw new JSONException("Nesting problem");
out.append(value); }
return this; beforeValue();
} this.out.append(value);
return this;
}
/** /**
* Encodes {@code value} to this stringer. * Encodes {@code value} to this stringer.
* *
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}. * {@link Double#isInfinite() infinities}.
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer value(double value) throws JSONException { */
if (stack.isEmpty()) { public JSONStringer value(double value) throws JSONException {
throw new JSONException("Nesting problem"); if (this.stack.isEmpty()) {
} throw new JSONException("Nesting problem");
beforeValue(); }
out.append(JSONObject.numberToString(value)); beforeValue();
return this; this.out.append(JSONObject.numberToString(value));
} return this;
}
/** /**
* Encodes {@code value} to this stringer. * Encodes {@code value} to this stringer.
* *
* @return this stringer. * @param value the value to encode
*/ * @return this stringer.
public JSONStringer value(long value) throws JSONException { * @throws JSONException if processing of json failed
if (stack.isEmpty()) { */
throw new JSONException("Nesting problem"); public JSONStringer value(long value) throws JSONException {
} if (this.stack.isEmpty()) {
beforeValue(); throw new JSONException("Nesting problem");
out.append(value); }
return this; beforeValue();
} this.out.append(value);
return this;
}
private void string(String value) { private void string(String value) {
out.append("\""); this.out.append("\"");
for (int i = 0, length = value.length(); i < length; i++) { for (int i = 0, length = value.length(); i < length; i++) {
char c = value.charAt(i); char c = value.charAt(i);
/* /*
* From RFC 4627, "All Unicode characters may be placed within the * From RFC 4627, "All Unicode characters may be placed within the
* quotation marks except for the characters that must be escaped: * quotation marks except for the characters that must be escaped:
* quotation mark, reverse solidus, and the control characters * quotation mark, reverse solidus, and the control characters
* (U+0000 through U+001F)." * (U+0000 through U+001F)."
*/ */
switch (c) { switch (c) {
case '"': case '"':
case '\\': case '\\':
case '/': case '/':
out.append('\\').append(c); this.out.append('\\').append(c);
break; break;
case '\t': case '\t':
out.append("\\t"); this.out.append("\\t");
break; break;
case '\b': case '\b':
out.append("\\b"); this.out.append("\\b");
break; break;
case '\n': case '\n':
out.append("\\n"); this.out.append("\\n");
break; break;
case '\r': case '\r':
out.append("\\r"); this.out.append("\\r");
break; break;
case '\f': case '\f':
out.append("\\f"); this.out.append("\\f");
break; break;
default: default:
if (c <= 0x1F) { if (c <= 0x1F) {
out.append(String.format("\\u%04x", (int) c)); this.out.append(String.format("\\u%04x", (int) c));
} else { }
out.append(c); else {
} this.out.append(c);
break; }
} break;
}
} }
out.append("\""); this.out.append("\"");
} }
private void newline() { private void newline() {
if (indent == null) { if (this.indent == null) {
return; return;
} }
out.append("\n"); this.out.append("\n");
for (int i = 0; i < stack.size(); i++) { for (int i = 0; i < this.stack.size(); i++) {
out.append(indent); this.out.append(this.indent);
} }
} }
/** /**
* Encodes the key (property name) to this stringer. * Encodes the key (property name) to this stringer.
* *
* @param name the name of the forthcoming value. May not be null. * @param name the name of the forthcoming value. May not be null.
* @return this stringer. * @return this stringer.
*/ * @throws JSONException if processing of json failed
public JSONStringer key(String name) throws JSONException { */
if (name == null) { public JSONStringer key(String name) throws JSONException {
throw new JSONException("Names must be non-null"); if (name == null) {
} throw new JSONException("Names must be non-null");
beforeKey(); }
string(name); beforeKey();
return this; string(name);
} return this;
}
/** /**
* Inserts any necessary separators and whitespace before a name. Also * Inserts any necessary separators and whitespace before a name. Also
* adjusts the stack to expect the key's value. * adjusts the stack to expect the key's value.
*/ * @throws JSONException if processing of json failed
private void beforeKey() throws JSONException { */
Scope context = peek(); private void beforeKey() throws JSONException {
if (context == Scope.NONEMPTY_OBJECT) { // first in object Scope context = peek();
out.append(','); if (context == Scope.NONEMPTY_OBJECT) { // first in object
} else if (context != Scope.EMPTY_OBJECT) { // not in an object! this.out.append(',');
throw new JSONException("Nesting problem"); }
} else if (context != Scope.EMPTY_OBJECT) { // not in an object!
newline(); throw new JSONException("Nesting problem");
replaceTop(Scope.DANGLING_KEY); }
} newline();
replaceTop(Scope.DANGLING_KEY);
}
/** /**
* Inserts any necessary separators and whitespace before a literal value, * Inserts any necessary separators and whitespace before a literal value,
* inline array, or inline object. Also adjusts the stack to expect either a * inline array, or inline object. Also adjusts the stack to expect either a
* closing bracket or another element. * closing bracket or another element.
*/ * @throws JSONException if processing of json failed
private void beforeValue() throws JSONException { */
if (stack.isEmpty()) { private void beforeValue() throws JSONException {
return; if (this.stack.isEmpty()) {
} return;
}
Scope context = peek(); Scope context = peek();
if (context == Scope.EMPTY_ARRAY) { // first in array if (context == Scope.EMPTY_ARRAY) { // first in array
replaceTop(Scope.NONEMPTY_ARRAY); replaceTop(Scope.NONEMPTY_ARRAY);
newline(); newline();
} else if (context == Scope.NONEMPTY_ARRAY) { // another in array }
out.append(','); else if (context == Scope.NONEMPTY_ARRAY) { // another in array
newline(); this.out.append(',');
} else if (context == Scope.DANGLING_KEY) { // value for key newline();
out.append(indent == null ? ":" : ": "); }
replaceTop(Scope.NONEMPTY_OBJECT); else if (context == Scope.DANGLING_KEY) { // value for key
} else if (context != Scope.NULL) { this.out.append(this.indent == null ? ":" : ": ");
throw new JSONException("Nesting problem"); replaceTop(Scope.NONEMPTY_OBJECT);
} }
} else if (context != Scope.NULL) {
throw new JSONException("Nesting problem");
}
}
/** /**
* Returns the encoded JSON string. * Returns the encoded JSON string.
* *
* <p>If invoked with unterminated arrays or unclosed objects, this method's * <p>If invoked with unterminated arrays or unclosed objects, this method's
* return value is undefined. * return value is undefined.
* *
* <p><strong>Warning:</strong> although it contradicts the general contract * <p><strong>Warning:</strong> although it contradicts the general contract
* of {@link Object#toString}, this method returns null if the stringer * of {@link Object#toString}, this method returns null if the stringer
* contains no data. * contains no data.
*/ * @return the encoded JSON string.
@Override public String toString() { */
return out.length() == 0 ? null : out.toString(); @Override
} public String toString() {
return this.out.length() == 0 ? null : this.out.toString();
}
} }