Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
4568f14c
Commit
4568f14c
authored
Jan 08, 2018
by
Stephane Nicoll
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix javadoc warnings
parent
966d4251
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
2444 additions
and
2266 deletions
+2444
-2266
JSON.java
...pringframework/boot/configurationprocessor/json/JSON.java
+103
-89
JSONArray.java
...framework/boot/configurationprocessor/json/JSONArray.java
+639
-564
JSONException.java
...ework/boot/configurationprocessor/json/JSONException.java
+3
-3
JSONObject.java
...ramework/boot/configurationprocessor/json/JSONObject.java
+788
-694
JSONStringer.java
...mework/boot/configurationprocessor/json/JSONStringer.java
+403
-369
JSONTokener.java
...amework/boot/configurationprocessor/json/JSONTokener.java
+508
-547
No files found.
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSON.java
View file @
4568f14c
...
...
@@ -17,100 +17,114 @@
package
org
.
springframework
.
boot
.
configurationprocessor
.
json
;
class
JSON
{
/**
* Returns the input if it is a JSON-permissible value; throws otherwise.
*/
static
double
checkDouble
(
double
d
)
throws
JSONException
{
if
(
Double
.
isInfinite
(
d
)
||
Double
.
isNaN
(
d
))
{
throw
new
JSONException
(
"Forbidden numeric value: "
+
d
);
}
return
d
;
}
/**
* Returns the input if it is a JSON-permissible value; throws otherwise.
*/
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
;
}
else
if
(
value
instanceof
String
)
{
String
stringValue
=
(
String
)
value
;
if
(
"true"
.
equalsIgnoreCase
(
stringValue
))
{
return
true
;
}
else
if
(
"false"
.
equalsIgnoreCase
(
stringValue
))
{
return
false
;
}
}
return
null
;
}
static
Boolean
toBoolean
(
Object
value
)
{
if
(
value
instanceof
Boolean
)
{
return
(
Boolean
)
value
;
}
else
if
(
value
instanceof
String
)
{
String
stringValue
=
(
String
)
value
;
if
(
"true"
.
equalsIgnoreCase
(
stringValue
))
{
return
true
;
}
else
if
(
"false"
.
equalsIgnoreCase
(
stringValue
))
{
return
false
;
}
}
return
null
;
}
static
Double
toDouble
(
Object
value
)
{
if
(
value
instanceof
Double
)
{
return
(
Double
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
doubleValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
Double
.
valueOf
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
Double
toDouble
(
Object
value
)
{
if
(
value
instanceof
Double
)
{
return
(
Double
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
doubleValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
Double
.
valueOf
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
Integer
toInteger
(
Object
value
)
{
if
(
value
instanceof
Integer
)
{
return
(
Integer
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
intValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
(
int
)
Double
.
parseDouble
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
Integer
toInteger
(
Object
value
)
{
if
(
value
instanceof
Integer
)
{
return
(
Integer
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
intValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
(
int
)
Double
.
parseDouble
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
Long
toLong
(
Object
value
)
{
if
(
value
instanceof
Long
)
{
return
(
Long
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
longValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
(
long
)
Double
.
parseDouble
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
Long
toLong
(
Object
value
)
{
if
(
value
instanceof
Long
)
{
return
(
Long
)
value
;
}
else
if
(
value
instanceof
Number
)
{
return
((
Number
)
value
).
longValue
();
}
else
if
(
value
instanceof
String
)
{
try
{
return
(
long
)
Double
.
parseDouble
((
String
)
value
);
}
catch
(
NumberFormatException
ignored
)
{
}
}
return
null
;
}
static
String
toString
(
Object
value
)
{
if
(
value
instanceof
String
)
{
return
(
String
)
value
;
}
else
if
(
value
!=
null
)
{
return
String
.
valueOf
(
value
);
}
return
null
;
}
static
String
toString
(
Object
value
)
{
if
(
value
instanceof
String
)
{
return
(
String
)
value
;
}
else
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."
);
}
else
{
throw
new
JSONException
(
"Value "
+
actual
+
" at "
+
indexOrName
+
" of type "
+
actual
.
getClass
().
getName
()
+
" cannot be converted to "
+
requiredType
);
}
}
public
static
JSONException
typeMismatch
(
Object
indexOrName
,
Object
actual
,
String
requiredType
)
throws
JSONException
{
if
(
actual
==
null
)
{
throw
new
JSONException
(
"Value at "
+
indexOrName
+
" is null."
);
}
else
{
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."
);
}
else
{
throw
new
JSONException
(
"Value "
+
actual
+
" 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."
);
}
else
{
throw
new
JSONException
(
"Value "
+
actual
+
" of type "
+
actual
.
getClass
().
getName
()
+
" cannot be converted to "
+
requiredType
);
}
}
}
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSONArray.java
View file @
4568f14c
...
...
@@ -48,568 +48,643 @@ import java.util.List;
*/
public
class
JSONArray
{
private
final
List
<
Object
>
values
;
/**
* Creates a {@code JSONArray} with no values.
*/
public
JSONArray
()
{
values
=
new
ArrayList
<
Object
>();
}
/**
* 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 */
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}.
*/
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
)
{
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.
*/
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
);
values
=
new
ArrayList
<
Object
>(
length
);
for
(
int
i
=
0
;
i
<
length
;
++
i
)
{
put
(
JSONObject
.
wrap
(
Array
.
get
(
array
,
i
)));
}
}
/**
* Returns the number of values in this array.
*/
public
int
length
()
{
return
values
.
size
();
}
/**
* Appends {@code value} to the end of this array.
*
* @return this array.
*/
public
JSONArray
put
(
boolean
value
)
{
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.
*/
public
JSONArray
put
(
double
value
)
throws
JSONException
{
values
.
add
(
JSON
.
checkDouble
(
value
));
return
this
;
}
/**
* Appends {@code value} to the end of this array.
*
* @return this array.
*/
public
JSONArray
put
(
int
value
)
{
values
.
add
(
value
);
return
this
;
}
/**
* Appends {@code value} to the end of this array.
*
* @return this array.
*/
public
JSONArray
put
(
long
value
)
{
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
)
{
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.
*
* @return this array.
*/
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 value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this array.
*/
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.
*
* @return this array.
*/
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.
*
* @return this array.
*/
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 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.
*/
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
(
values
.
size
()
<=
index
)
{
values
.
add
(
null
);
}
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}.
*/
public
boolean
isNull
(
int
index
)
{
Object
value
=
opt
(
index
);
return
value
==
null
||
value
==
JSONObject
.
NULL
;
}
/**
* Returns 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
=
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.."
+
values
.
size
()
+
")"
);
}
}
/**
* Returns the value at {@code index}, or null if the array has no value
* at {@code index}.
*/
public
Object
opt
(
int
index
)
{
if
(
index
<
0
||
index
>=
values
.
size
())
{
return
null
;
}
return
values
.
get
(
index
);
}
/**
* Removes and returns the value at {@code index}, or null if the array has no value
* at {@code index}.
*/
public
Object
remove
(
int
index
)
{
if
(
index
<
0
||
index
>=
values
.
size
())
{
return
null
;
}
return
values
.
remove
(
index
);
}
/**
* Returns the value at {@code index} if it exists and is a boolean or can
* be coerced to a boolean.
*
* @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.
*/
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.
*/
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.
*
* @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.
*/
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.
*/
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.
*
* @throws JSONException if the value at {@code index} doesn't exist or
* cannot be coerced to a 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.
*/
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.
*/
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.
*
* @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.
*/
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.
*/
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.
*
* @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.
*/
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.
*/
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}.
*
* @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.
*/
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}.
*
* @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.
*/
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.
*/
public
JSONObject
toJSONObject
(
JSONArray
names
)
throws
JSONException
{
JSONObject
result
=
new
JSONObject
();
int
length
=
Math
.
min
(
names
.
length
(),
values
.
size
());
if
(
length
==
0
)
{
return
null
;
}
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
String
name
=
JSON
.
toString
(
names
.
opt
(
i
));
result
.
put
(
name
,
opt
(
i
));
}
return
result
;
}
/**
* Returns a new string by alternating this array's values with {@code
* separator}. This array's string values are quoted and have their special
* characters escaped. For example, the array containing the strings '12"
* pizza', 'taco' and 'soda' joined on '+' returns this:
* <pre>"12\" pizza"+"taco"+"soda"</pre>
*/
public
String
join
(
String
separator
)
throws
JSONException
{
JSONStringer
stringer
=
new
JSONStringer
();
stringer
.
open
(
JSONStringer
.
Scope
.
NULL
,
""
);
for
(
int
i
=
0
,
size
=
values
.
size
();
i
<
size
;
i
++)
{
if
(
i
>
0
)
{
stringer
.
out
.
append
(
separator
);
}
stringer
.
value
(
values
.
get
(
i
));
}
stringer
.
close
(
JSONStringer
.
Scope
.
NULL
,
JSONStringer
.
Scope
.
NULL
,
""
);
return
stringer
.
out
.
toString
();
}
/**
* Encodes this array as a compact JSON string, such as:
* <pre>[94043,90210]</pre>
*/
@Override
public
String
toString
()
{
try
{
JSONStringer
stringer
=
new
JSONStringer
();
writeTo
(
stringer
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
return
null
;
}
}
/**
* Encodes this array as a human readable JSON string for debugging, such
* as:
* <pre>
* [
* 94043,
* 90210
* ]</pre>
*
* @param indentSpaces the number of spaces to indent for each level of
* nesting.
*/
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
:
values
)
{
stringer
.
value
(
value
);
}
stringer
.
endArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
JSONArray
&&
((
JSONArray
)
o
).
values
.
equals
(
values
);
}
@Override
public
int
hashCode
()
{
// diverge from the original, which doesn't implement hashCode
return
values
.
hashCode
();
}
private
final
List
<
Object
>
values
;
/**
* Creates a {@code JSONArray} with no values.
*/
public
JSONArray
()
{
this
.
values
=
new
ArrayList
<
Object
>();
}
/**
* 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 */
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
<
Object
>(
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 a int.
*/
public
int
getInt
(
int
index
)
throws
JSONException
{
Object
object
=
get
(
index
);
Integer
result
=
JSON
.
toInteger
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
index
,
object
,
"int"
);
}
return
result
;
}
/**
* Returns the value at {@code index} if it exists and is an int or
* can be coerced to an int. Returns 0 otherwise.
* @param index the index to get the value from
* @return the {@code value} or {@code 0}
*/
public
int
optInt
(
int
index
)
{
return
optInt
(
index
,
0
);
}
/**
* Returns the value at {@code index} if it exists and is an int or
* can be coerced to an int. Returns {@code fallback} otherwise.
* @param index the index to get the value from
* @param fallback the fallback value
* @return the value at {@code index} of {@code fallback}
*/
public
int
optInt
(
int
index
,
int
fallback
)
{
Object
object
=
opt
(
index
);
Integer
result
=
JSON
.
toInteger
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value at {@code index} if it exists and is a long or
* can be coerced to a long.
* @param index the index to get the value from
* @return the {@code value}
*
* @throws JSONException if the value at {@code index} doesn't exist or
* cannot be coerced to a long.
*/
public
long
getLong
(
int
index
)
throws
JSONException
{
Object
object
=
get
(
index
);
Long
result
=
JSON
.
toLong
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
index
,
object
,
"long"
);
}
return
result
;
}
/**
* Returns the value at {@code index} if it exists and is a long or
* can be coerced to a long. Returns 0 otherwise.
* @param index the index to get the value from
* @return the {@code value} or {@code 0}
*/
public
long
optLong
(
int
index
)
{
return
optLong
(
index
,
0L
);
}
/**
* Returns the value at {@code index} if it exists and is a long or
* can be coerced to a long. Returns {@code fallback} otherwise.
* @param index the index to get the value from
* @param fallback the fallback value
* @return the value at {@code index} of {@code fallback}
*/
public
long
optLong
(
int
index
,
long
fallback
)
{
Object
object
=
opt
(
index
);
Long
result
=
JSON
.
toLong
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value at {@code index} if it exists, coercing it if
* necessary.
* @param index the index to get the value from
* @return the {@code value}
* @throws JSONException if no such value exists.
*/
public
String
getString
(
int
index
)
throws
JSONException
{
Object
object
=
get
(
index
);
String
result
=
JSON
.
toString
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
index
,
object
,
"String"
);
}
return
result
;
}
/**
* Returns the value at {@code index} if it exists, coercing it if
* necessary. Returns the empty string if no such value exists.
* @param index the index to get the value from
* @return the {@code value} or an empty string
*/
public
String
optString
(
int
index
)
{
return
optString
(
index
,
""
);
}
/**
* Returns the value at {@code index} if it exists, coercing it if
* necessary. Returns {@code fallback} if no such value exists.
* @param index the index to get the value from
* @param fallback the fallback value
* @return the value at {@code index} of {@code fallback}
*/
public
String
optString
(
int
index
,
String
fallback
)
{
Object
object
=
opt
(
index
);
String
result
=
JSON
.
toString
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value at {@code index} if it exists and is a {@code
* JSONArray}.
* @param index the index to get the value from
* @return the array at {@code index}
* @throws JSONException if the value doesn't exist or is not a {@code
* JSONArray}.
*/
public
JSONArray
getJSONArray
(
int
index
)
throws
JSONException
{
Object
object
=
get
(
index
);
if
(
object
instanceof
JSONArray
)
{
return
(
JSONArray
)
object
;
}
else
{
throw
JSON
.
typeMismatch
(
index
,
object
,
"JSONArray"
);
}
}
/**
* Returns the value at {@code index} if it exists and is a {@code
* JSONArray}. Returns null otherwise.
* @param index the index to get the value from
* @return the array at {@code index} or {@code null}
*/
public
JSONArray
optJSONArray
(
int
index
)
{
Object
object
=
opt
(
index
);
return
object
instanceof
JSONArray
?
(
JSONArray
)
object
:
null
;
}
/**
* Returns the value at {@code index} if it exists and is a {@code
* JSONObject}.
* @param index the index to get the value from
* @return the object at {@code index}
* @throws JSONException if the value doesn't exist or is not a {@code
* JSONObject}.
*/
public
JSONObject
getJSONObject
(
int
index
)
throws
JSONException
{
Object
object
=
get
(
index
);
if
(
object
instanceof
JSONObject
)
{
return
(
JSONObject
)
object
;
}
else
{
throw
JSON
.
typeMismatch
(
index
,
object
,
"JSONObject"
);
}
}
/**
* Returns the value at {@code index} if it exists and is a {@code
* JSONObject}. Returns null otherwise.
* @param index the index to get the value from
* @return the object at {@code index} or {@code null}
*/
public
JSONObject
optJSONObject
(
int
index
)
{
Object
object
=
opt
(
index
);
return
object
instanceof
JSONObject
?
(
JSONObject
)
object
:
null
;
}
/**
* Returns a new object whose values are the values in this array, and whose
* names are the values in {@code names}. Names and values are paired up by
* index from 0 through to the shorter array's length. Names that are not
* strings will be coerced to strings. This method returns null if either
* array is empty.
* @param names the property names
* @return a json object
* @throws JSONException if processing of json failed
*/
public
JSONObject
toJSONObject
(
JSONArray
names
)
throws
JSONException
{
JSONObject
result
=
new
JSONObject
();
int
length
=
Math
.
min
(
names
.
length
(),
this
.
values
.
size
());
if
(
length
==
0
)
{
return
null
;
}
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
String
name
=
JSON
.
toString
(
names
.
opt
(
i
));
result
.
put
(
name
,
opt
(
i
));
}
return
result
;
}
/**
* Returns a new string by alternating this array's values with {@code
* separator}. This array's string values are quoted and have their special
* characters escaped. For example, the array containing the strings '12"
* pizza', 'taco' and 'soda' joined on '+' returns this:
* <pre>"12\" pizza"+"taco"+"soda"</pre>
* @param separator the separator to use
* @return the joined value
* @throws JSONException if processing of json failed
*/
public
String
join
(
String
separator
)
throws
JSONException
{
JSONStringer
stringer
=
new
JSONStringer
();
stringer
.
open
(
JSONStringer
.
Scope
.
NULL
,
""
);
for
(
int
i
=
0
,
size
=
this
.
values
.
size
();
i
<
size
;
i
++)
{
if
(
i
>
0
)
{
stringer
.
out
.
append
(
separator
);
}
stringer
.
value
(
this
.
values
.
get
(
i
));
}
stringer
.
close
(
JSONStringer
.
Scope
.
NULL
,
JSONStringer
.
Scope
.
NULL
,
""
);
return
stringer
.
out
.
toString
();
}
/**
* Encodes this array as a compact JSON string, such as:
* <pre>[94043,90210]</pre>
* @return a compact JSON string representation of this array
*/
@Override
public
String
toString
()
{
try
{
JSONStringer
stringer
=
new
JSONStringer
();
writeTo
(
stringer
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
return
null
;
}
}
/**
* Encodes this array as a human readable JSON string for debugging, such
* as:
* <pre>
* [
* 94043,
* 90210
* ]</pre>
*
* @param indentSpaces the number of spaces to indent for each level of
* nesting.
* @return a human readable JSON string of this array
* @throws JSONException if processing of json failed
*/
public
String
toString
(
int
indentSpaces
)
throws
JSONException
{
JSONStringer
stringer
=
new
JSONStringer
(
indentSpaces
);
writeTo
(
stringer
);
return
stringer
.
toString
();
}
void
writeTo
(
JSONStringer
stringer
)
throws
JSONException
{
stringer
.
array
();
for
(
Object
value
:
this
.
values
)
{
stringer
.
value
(
value
);
}
stringer
.
endArray
();
}
@Override
public
boolean
equals
(
Object
o
)
{
return
o
instanceof
JSONArray
&&
((
JSONArray
)
o
).
values
.
equals
(
this
.
values
);
}
@Override
public
int
hashCode
()
{
// diverge from the original, which doesn't implement hashCode
return
this
.
values
.
hashCode
();
}
}
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSONException.java
View file @
4568f14c
...
...
@@ -43,7 +43,7 @@ package org.springframework.boot.configurationprocessor.json;
*/
public
class
JSONException
extends
Exception
{
public
JSONException
(
String
s
)
{
super
(
s
);
}
public
JSONException
(
String
s
)
{
super
(
s
);
}
}
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSONObject.java
View file @
4568f14c
...
...
@@ -78,698 +78,792 @@ import java.util.Map;
*/
public
class
JSONObject
{
private
static
final
Double
NEGATIVE_ZERO
=
-
0
d
;
/**
* A sentinel value used to explicitly define a name with no value. Unlike
* {@code null}, names with this value:
* <ul>
* <li>show up in the {@link #names} array
* <li>show up in the {@link #keys} iterator
* <li>return {@code true} for {@link #has(String)}
* <li>do not throw on {@link #get(String)}
* <li>are included in the encoded JSON string.
* </ul>
*
* <p>This value violates the general contract of {@link Object#equals} by
* returning true when compared to {@code null}. Its {@link #toString}
* method returns "null".
*/
public
static
final
Object
NULL
=
new
Object
()
{
@Override
public
boolean
equals
(
Object
o
)
{
return
o
==
this
||
o
==
null
;
// API specifies this broken equals implementation
}
@Override
public
String
toString
()
{
return
"null"
;
}
};
private
final
Map
<
String
,
Object
>
nameValuePairs
;
/**
* Creates a {@code JSONObject} with no name/value mappings.
*/
public
JSONObject
()
{
nameValuePairs
=
new
HashMap
<
String
,
Object
>();
}
/**
* 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) */
public
JSONObject
(
Map
copyFrom
)
{
this
();
Map
<?,
?>
contentsTyped
=
(
Map
<?,
?>)
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"
);
}
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.
*/
public
JSONObject
(
JSONObject
copyFrom
,
String
[]
names
)
throws
JSONException
{
this
();
for
(
String
name
:
names
)
{
Object
value
=
copyFrom
.
opt
(
name
);
if
(
value
!=
null
)
{
nameValuePairs
.
put
(
name
,
value
);
}
}
}
/**
* Returns the number of name/value mappings in this object.
*/
public
int
length
()
{
return
nameValuePairs
.
size
();
}
/**
* Maps {@code name} to {@code value}, clobbering any existing name/value
* mapping with the same name.
*
* @return this object.
*/
public
JSONObject
put
(
String
name
,
boolean
value
)
throws
JSONException
{
nameValuePairs
.
put
(
checkName
(
name
),
value
);
return
this
;
}
/**
* Maps {@code name} to {@code value}, clobbering any existing name/value
* mapping with the same name.
*
* @param value a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return this object.
*/
public
JSONObject
put
(
String
name
,
double
value
)
throws
JSONException
{
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.
*
* @return this object.
*/
public
JSONObject
put
(
String
name
,
int
value
)
throws
JSONException
{
nameValuePairs
.
put
(
checkName
(
name
),
value
);
return
this
;
}
/**
* Maps {@code name} to {@code value}, clobbering any existing name/value
* mapping with the same name.
*
* @return this object.
*/
public
JSONObject
put
(
String
name
,
long
value
)
throws
JSONException
{
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 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.
*/
public
JSONObject
put
(
String
name
,
Object
value
)
throws
JSONException
{
if
(
value
==
null
)
{
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
());
}
nameValuePairs
.
put
(
checkName
(
name
),
value
);
return
this
;
}
/**
* Equivalent to {@code put(name, value)} when both parameters are non-null;
* does nothing otherwise.
*/
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 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}.
*/
public
JSONObject
accumulate
(
String
name
,
Object
value
)
throws
JSONException
{
Object
current
=
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
)
{
JSONArray
array
=
(
JSONArray
)
current
;
array
.
put
(
value
);
}
else
{
JSONArray
array
=
new
JSONArray
();
array
.
put
(
current
);
array
.
put
(
value
);
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.
*
* @return the value previously mapped by {@code name}, or null if there was
* no such mapping.
*/
public
Object
remove
(
String
name
)
{
return
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}.
*/
public
boolean
isNull
(
String
name
)
{
Object
value
=
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}.
*/
public
boolean
has
(
String
name
)
{
return
nameValuePairs
.
containsKey
(
name
);
}
/**
* Returns the value mapped by {@code name}.
*
* @throws JSONException if no such mapping exists.
*/
public
Object
get
(
String
name
)
throws
JSONException
{
Object
result
=
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.
*/
public
Object
opt
(
String
name
)
{
return
nameValuePairs
.
get
(
name
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a boolean or
* can be coerced to a boolean.
*
* @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.
*/
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.
*/
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.
*
* @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.
*/
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.
*/
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.
*
* @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.
*/
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.
*/
public
int
optInt
(
String
name
,
int
fallback
)
{
Object
object
=
opt
(
name
);
Integer
result
=
JSON
.
toInteger
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Note that JSON represents numbers as doubles,
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
*
* @throws JSONException if the mapping doesn't exist or cannot be coerced
* to a long.
*/
public
long
getLong
(
String
name
)
throws
JSONException
{
Object
object
=
get
(
name
);
Long
result
=
JSON
.
toLong
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
name
,
object
,
"long"
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as doubles,
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
*/
public
long
optLong
(
String
name
)
{
return
optLong
(
name
,
0L
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents
* numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
* numbers via JSON.
*/
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.
*
* @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.
*/
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.
*/
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}.
*
* @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.
*/
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}.
*
* @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.
*/
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.
*/
public
JSONArray
toJSONArray
(
JSONArray
names
)
throws
JSONException
{
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 a raw type for API compatibility */
public
Iterator
keys
()
{
return
nameValuePairs
.
keySet
().
iterator
();
}
/**
* Returns an array containing the string names in this object. This method
* returns null if this object contains no mappings.
*/
public
JSONArray
names
()
{
return
nameValuePairs
.
isEmpty
()
?
null
:
new
JSONArray
(
new
ArrayList
<
String
>(
nameValuePairs
.
keySet
()));
}
/**
* Encodes this object as a compact JSON string, such as:
* <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
*/
@Override
public
String
toString
()
{
try
{
JSONStringer
stringer
=
new
JSONStringer
();
writeTo
(
stringer
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
return
null
;
}
}
/**
* Encodes this object as a human readable JSON string for debugging, such
* as:
* <pre>
* {
* "query": "Pizza",
* "locations": [
* 94043,
* 90210
* ]
* }</pre>
*
* @param indentSpaces the number of spaces to indent for each level of
* nesting.
*/
public
String
toString
(
int
indentSpaces
)
throws
JSONException
{
JSONStringer
stringer
=
new
JSONStringer
(
indentSpaces
);
writeTo
(
stringer
);
return
stringer
.
toString
();
}
void
writeTo
(
JSONStringer
stringer
)
throws
JSONException
{
stringer
.
object
();
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
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}.
*/
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
==
(
double
)
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.
*/
public
static
String
quote
(
String
data
)
{
if
(
data
==
null
)
{
return
"\"\""
;
}
try
{
JSONStringer
stringer
=
new
JSONStringer
();
stringer
.
open
(
JSONStringer
.
Scope
.
NULL
,
""
);
stringer
.
value
(
data
);
stringer
.
close
(
JSONStringer
.
Scope
.
NULL
,
JSONStringer
.
Scope
.
NULL
,
""
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
throw
new
AssertionError
();
}
}
/**
* Wraps the given object if necessary.
*
* <p>If the object is null or , returns {@link #NULL}.
* If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary.
* If the object is {@code NULL}, no wrapping is necessary.
* If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
* If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
* If the object is a primitive wrapper type or {@code String}, returns the object.
* Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
* If wrapping fails, returns null.
*/
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
ignored
)
{
}
return
null
;
}
private
static
final
Double
NEGATIVE_ZERO
=
-
0
d
;
/**
* A sentinel value used to explicitly define a name with no value. Unlike
* {@code null}, names with this value:
* <ul>
* <li>show up in the {@link #names} array
* <li>show up in the {@link #keys} iterator
* <li>return {@code true} for {@link #has(String)}
* <li>do not throw on {@link #get(String)}
* <li>are included in the encoded JSON string.
* </ul>
*
* <p>This value violates the general contract of {@link Object#equals} by
* returning true when compared to {@code null}. Its {@link #toString}
* method returns "null".
*/
public
static
final
Object
NULL
=
new
Object
()
{
@Override
public
boolean
equals
(
Object
o
)
{
return
o
==
this
||
o
==
null
;
// API specifies this broken equals implementation
}
@Override
public
String
toString
()
{
return
"null"
;
}
};
private
final
Map
<
String
,
Object
>
nameValuePairs
;
/**
* Creates a {@code JSONObject} with no name/value mappings.
*/
public
JSONObject
()
{
this
.
nameValuePairs
=
new
HashMap
<
String
,
Object
>();
}
/**
* 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) */
public
JSONObject
(
Map
copyFrom
)
{
this
();
Map
<?,
?>
contentsTyped
=
(
Map
<?,
?>)
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
)
{
JSONArray
array
=
(
JSONArray
)
current
;
array
.
put
(
value
);
}
else
{
JSONArray
array
=
new
JSONArray
();
array
.
put
(
current
);
array
.
put
(
value
);
this
.
nameValuePairs
.
put
(
name
,
array
);
}
return
this
;
}
String
checkName
(
String
name
)
throws
JSONException
{
if
(
name
==
null
)
{
throw
new
JSONException
(
"Names must be non-null"
);
}
return
name
;
}
/**
* Removes the named mapping if it exists; does nothing otherwise.
*
* @param name the name of the property
* @return the value previously mapped by {@code name}, or null if there was
* no such mapping.
*/
public
Object
remove
(
String
name
)
{
return
this
.
nameValuePairs
.
remove
(
name
);
}
/**
* Returns true if this object has no mapping for {@code name} or if it has
* a mapping whose value is {@link #NULL}.
* @param name the name of the property
* @return true if this object has no mapping for {@code name}
*/
public
boolean
isNull
(
String
name
)
{
Object
value
=
this
.
nameValuePairs
.
get
(
name
);
return
value
==
null
||
value
==
NULL
;
}
/**
* Returns true if this object has a mapping for {@code name}. The mapping
* may be {@link #NULL}.
* @param name the name of the property
* @return true if this object has a mapping for {@code name}
*/
public
boolean
has
(
String
name
)
{
return
this
.
nameValuePairs
.
containsKey
(
name
);
}
/**
* Returns the value mapped by {@code name}.
* @param name the name of the property
* @return the value
* @throws JSONException if no such mapping exists.
*/
public
Object
get
(
String
name
)
throws
JSONException
{
Object
result
=
this
.
nameValuePairs
.
get
(
name
);
if
(
result
==
null
)
{
throw
new
JSONException
(
"No value for "
+
name
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name}, or null if no such mapping
* exists.
* @param name the name of the property
* @return the value or {@code null}
*/
public
Object
opt
(
String
name
)
{
return
this
.
nameValuePairs
.
get
(
name
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a boolean or
* can be coerced to a boolean.
*
* @param name the name of the property
* @return the value
* @throws JSONException if the mapping doesn't exist or cannot be coerced
* to a boolean.
*/
public
boolean
getBoolean
(
String
name
)
throws
JSONException
{
Object
object
=
get
(
name
);
Boolean
result
=
JSON
.
toBoolean
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
name
,
object
,
"boolean"
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a boolean or
* can be coerced to a boolean. Returns false otherwise.
* @param name the name of the property
* @return the value or {@code null}
*/
public
boolean
optBoolean
(
String
name
)
{
return
optBoolean
(
name
,
false
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a boolean or
* can be coerced to a boolean. Returns {@code fallback} otherwise.
* @param name the name of the property
* @param fallback a fallback value
* @return the value or {@code fallback}
*/
public
boolean
optBoolean
(
String
name
,
boolean
fallback
)
{
Object
object
=
opt
(
name
);
Boolean
result
=
JSON
.
toBoolean
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a double or
* can be coerced to a double.
*
* @param name the name of the property
* @return the value
* @throws JSONException if the mapping doesn't exist or cannot be coerced
* to a double.
*/
public
double
getDouble
(
String
name
)
throws
JSONException
{
Object
object
=
get
(
name
);
Double
result
=
JSON
.
toDouble
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
name
,
object
,
"double"
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a double or
* can be coerced to a double. Returns {@code NaN} otherwise.
* @param name the name of the property
* @return the value or {@code NaN}
*/
public
double
optDouble
(
String
name
)
{
return
optDouble
(
name
,
Double
.
NaN
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a double or
* can be coerced to a double. Returns {@code fallback} otherwise.
* @param name the name of the property
* @param fallback a fallback value
* @return the value or {@code fallback}
*/
public
double
optDouble
(
String
name
,
double
fallback
)
{
Object
object
=
opt
(
name
);
Double
result
=
JSON
.
toDouble
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value mapped by {@code name} if it exists and is an int or
* can be coerced to an int.
* @param name the name of the property
* @return the value
* @throws JSONException if the mapping doesn't exist or cannot be coerced
* to an int.
*/
public
int
getInt
(
String
name
)
throws
JSONException
{
Object
object
=
get
(
name
);
Integer
result
=
JSON
.
toInteger
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
name
,
object
,
"int"
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name} if it exists and is an int or
* can be coerced to an int. Returns 0 otherwise.
* @param name the name of the property
* @return the value of {@code 0}
*/
public
int
optInt
(
String
name
)
{
return
optInt
(
name
,
0
);
}
/**
* Returns the value mapped by {@code name} if it exists and is an int or
* can be coerced to an int. Returns {@code fallback} otherwise.
* @param name the name of the property
* @param fallback a fallback value
* @return the value or {@code fallback}
*/
public
int
optInt
(
String
name
,
int
fallback
)
{
Object
object
=
opt
(
name
);
Integer
result
=
JSON
.
toInteger
(
object
);
return
result
!=
null
?
result
:
fallback
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Note that JSON represents numbers as doubles,
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
*
* @param name the name of the property
* @return the value
* @throws JSONException if the mapping doesn't exist or cannot be coerced
* to a long.
*/
public
long
getLong
(
String
name
)
throws
JSONException
{
Object
object
=
get
(
name
);
Long
result
=
JSON
.
toLong
(
object
);
if
(
result
==
null
)
{
throw
JSON
.
typeMismatch
(
name
,
object
,
"long"
);
}
return
result
;
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Returns 0 otherwise. Note that JSON represents numbers as doubles,
* so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
* @param name the name of the property
* @return the value or {@code 0L}
*/
public
long
optLong
(
String
name
)
{
return
optLong
(
name
,
0L
);
}
/**
* Returns the value mapped by {@code name} if it exists and is a long or
* can be coerced to a long. Returns {@code fallback} otherwise. Note that JSON represents
* numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
* numbers via 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 */
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
<
String
>(
this
.
nameValuePairs
.
keySet
()));
}
/**
* Encodes this object as a compact JSON string, such as:
* <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
* @return a string representation of the object.
*/
@Override
public
String
toString
()
{
try
{
JSONStringer
stringer
=
new
JSONStringer
();
writeTo
(
stringer
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
return
null
;
}
}
/**
* Encodes this object as a human readable JSON string for debugging, such
* as:
* <pre>
* {
* "query": "Pizza",
* "locations": [
* 94043,
* 90210
* ]
* }</pre>
*
* @param indentSpaces the number of spaces to indent for each level of
* nesting.
* @return a string representation of the object.
* @throws JSONException if an error occurs
*/
public
String
toString
(
int
indentSpaces
)
throws
JSONException
{
JSONStringer
stringer
=
new
JSONStringer
(
indentSpaces
);
writeTo
(
stringer
);
return
stringer
.
toString
();
}
void
writeTo
(
JSONStringer
stringer
)
throws
JSONException
{
stringer
.
object
();
for
(
Map
.
Entry
<
String
,
Object
>
entry
:
this
.
nameValuePairs
.
entrySet
())
{
stringer
.
key
(
entry
.
getKey
()).
value
(
entry
.
getValue
());
}
stringer
.
endObject
();
}
/**
* Encodes the number as a JSON string.
*
* @param number a finite value. May not be {@link Double#isNaN() NaNs} or
* {@link Double#isInfinite() infinities}.
* @return the encoded value
* @throws JSONException if an error occurs
*/
public
static
String
numberToString
(
Number
number
)
throws
JSONException
{
if
(
number
==
null
)
{
throw
new
JSONException
(
"Number must be non-null"
);
}
double
doubleValue
=
number
.
doubleValue
();
JSON
.
checkDouble
(
doubleValue
);
// the original returns "-0" instead of "-0.0" for negative zero
if
(
number
.
equals
(
NEGATIVE_ZERO
))
{
return
"-0"
;
}
long
longValue
=
number
.
longValue
();
if
(
doubleValue
==
(
double
)
longValue
)
{
return
Long
.
toString
(
longValue
);
}
return
number
.
toString
();
}
/**
* Encodes {@code data} as a JSON string. This applies quotes and any
* necessary character escaping.
*
* @param data the string to encode. Null will be interpreted as an empty
* string.
* @return the quoted value
*/
public
static
String
quote
(
String
data
)
{
if
(
data
==
null
)
{
return
"\"\""
;
}
try
{
JSONStringer
stringer
=
new
JSONStringer
();
stringer
.
open
(
JSONStringer
.
Scope
.
NULL
,
""
);
stringer
.
value
(
data
);
stringer
.
close
(
JSONStringer
.
Scope
.
NULL
,
JSONStringer
.
Scope
.
NULL
,
""
);
return
stringer
.
toString
();
}
catch
(
JSONException
e
)
{
throw
new
AssertionError
();
}
}
/**
* Wraps the given object if necessary.
*
* <p>If the object is null or , returns {@link #NULL}.
* If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary.
* If the object is {@code NULL}, no wrapping is necessary.
* If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
* If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
* If the object is a primitive wrapper type or {@code String}, returns the object.
* Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
* If wrapping fails, returns null.
* @param o the object to wrap
* @return the wrapped object
*/
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
ignored
)
{
}
return
null
;
}
}
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSONStringer.java
View file @
4568f14c
...
...
@@ -60,373 +60,407 @@ import java.util.List;
*/
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 (ie. 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
,
/**
* A array with at least one value requires a comma and newline before
* the next element.
*/
NONEMPTY_ARRAY
,
/**
* An object with no keys or values requires no separators or newlines
* before it is closed.
*/
EMPTY_OBJECT
,
/**
* An object whose most recent element is a key. The next element must
* be a value.
*/
DANGLING_KEY
,
/**
* An object with at least one name/value pair requires a comma and
* newline before the next element.
*/
NONEMPTY_OBJECT
,
/**
* A special bracketless array needed by JSONStringer.join() and
* JSONObject.quote() only. Not used for JSON encoding.
*/
NULL
,
}
/**
* Unlike the original implementation, this stack isn't limited to 20
* levels of nesting.
*/
private
final
List
<
Scope
>
stack
=
new
ArrayList
<
Scope
>();
/**
* 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
()
{
indent
=
null
;
}
JSONStringer
(
int
indentSpaces
)
{
char
[]
indentChars
=
new
char
[
indentSpaces
];
Arrays
.
fill
(
indentChars
,
' '
);
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.
*/
public
JSONStringer
array
()
throws
JSONException
{
return
open
(
Scope
.
EMPTY_ARRAY
,
"["
);
}
/**
* Ends encoding the current array.
*
* @return this stringer.
*/
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.
*/
public
JSONStringer
object
()
throws
JSONException
{
return
open
(
Scope
.
EMPTY_OBJECT
,
"{"
);
}
/**
* Ends encoding the current object.
*
* @return this stringer.
*/
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.
*/
JSONStringer
open
(
Scope
empty
,
String
openBracket
)
throws
JSONException
{
if
(
stack
.
isEmpty
()
&&
out
.
length
()
>
0
)
{
throw
new
JSONException
(
"Nesting problem: multiple top-level roots"
);
}
beforeValue
();
stack
.
add
(
empty
);
out
.
append
(
openBracket
);
return
this
;
}
/**
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
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
);
if
(
context
==
nonempty
)
{
newline
();
}
out
.
append
(
closeBracket
);
return
this
;
}
/**
* Returns the value on the top of the stack.
*/
private
Scope
peek
()
throws
JSONException
{
if
(
stack
.
isEmpty
())
{
throw
new
JSONException
(
"Nesting problem"
);
}
return
stack
.
get
(
stack
.
size
()
-
1
);
}
/**
* Replace the value on the top of the stack with the given value.
*/
private
void
replaceTop
(
Scope
topOfStack
)
{
stack
.
set
(
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.
*/
public
JSONStringer
value
(
Object
value
)
throws
JSONException
{
if
(
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
)
{
out
.
append
(
value
);
}
else
if
(
value
instanceof
Number
)
{
out
.
append
(
JSONObject
.
numberToString
((
Number
)
value
));
}
else
{
string
(
value
.
toString
());
}
return
this
;
}
/**
* Encodes {@code value} to this stringer.
*
* @return this stringer.
*/
public
JSONStringer
value
(
boolean
value
)
throws
JSONException
{
if
(
stack
.
isEmpty
())
{
throw
new
JSONException
(
"Nesting problem"
);
}
beforeValue
();
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.
*/
public
JSONStringer
value
(
double
value
)
throws
JSONException
{
if
(
stack
.
isEmpty
())
{
throw
new
JSONException
(
"Nesting problem"
);
}
beforeValue
();
out
.
append
(
JSONObject
.
numberToString
(
value
));
return
this
;
}
/**
* Encodes {@code value} to this stringer.
*
* @return this stringer.
*/
public
JSONStringer
value
(
long
value
)
throws
JSONException
{
if
(
stack
.
isEmpty
())
{
throw
new
JSONException
(
"Nesting problem"
);
}
beforeValue
();
out
.
append
(
value
);
return
this
;
}
private
void
string
(
String
value
)
{
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
'"'
:
case
'\\'
:
case
'/'
:
out
.
append
(
'\\'
).
append
(
c
);
break
;
case
'\t'
:
out
.
append
(
"\\t"
);
break
;
case
'\b'
:
out
.
append
(
"\\b"
);
break
;
case
'\n'
:
out
.
append
(
"\\n"
);
break
;
case
'\r'
:
out
.
append
(
"\\r"
);
break
;
case
'\f'
:
out
.
append
(
"\\f"
);
break
;
default
:
if
(
c
<=
0x1F
)
{
out
.
append
(
String
.
format
(
"\\u%04x"
,
(
int
)
c
));
}
else
{
out
.
append
(
c
);
}
break
;
}
}
out
.
append
(
"\""
);
}
private
void
newline
()
{
if
(
indent
==
null
)
{
return
;
}
out
.
append
(
"\n"
);
for
(
int
i
=
0
;
i
<
stack
.
size
();
i
++)
{
out
.
append
(
indent
);
}
}
/**
* Encodes the key (property name) to this stringer.
*
* @param name the name of the forthcoming value. May not be null.
* @return this stringer.
*/
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.
*/
private
void
beforeKey
()
throws
JSONException
{
Scope
context
=
peek
();
if
(
context
==
Scope
.
NONEMPTY_OBJECT
)
{
// first in object
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.
*/
private
void
beforeValue
()
throws
JSONException
{
if
(
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
out
.
append
(
','
);
newline
();
}
else
if
(
context
==
Scope
.
DANGLING_KEY
)
{
// value for key
out
.
append
(
indent
==
null
?
":"
:
": "
);
replaceTop
(
Scope
.
NONEMPTY_OBJECT
);
}
else
if
(
context
!=
Scope
.
NULL
)
{
throw
new
JSONException
(
"Nesting problem"
);
}
}
/**
* Returns the encoded JSON string.
*
* <p>If invoked with unterminated arrays or unclosed objects, this method's
* return value is undefined.
*
* <p><strong>Warning:</strong> although it contradicts the general contract
* of {@link Object#toString}, this method returns null if the stringer
* contains no data.
*/
@Override
public
String
toString
()
{
return
out
.
length
()
==
0
?
null
:
out
.
toString
();
}
/** 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 (ie. 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
,
/**
* A array with at least one value requires a comma and newline before
* the next element.
*/
NONEMPTY_ARRAY
,
/**
* An object with no keys or values requires no separators or newlines
* before it is closed.
*/
EMPTY_OBJECT
,
/**
* An object whose most recent element is a key. The next element must
* be a value.
*/
DANGLING_KEY
,
/**
* An object with at least one name/value pair requires a comma and
* newline before the next element.
*/
NONEMPTY_OBJECT
,
/**
* A special bracketless array needed by JSONStringer.join() and
* JSONObject.quote() only. Not used for JSON encoding.
*/
NULL
,
}
/**
* Unlike the original implementation, this stack isn't limited to 20
* levels of nesting.
*/
private
final
List
<
Scope
>
stack
=
new
ArrayList
<
Scope
>();
/**
* 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
.
length
()
>
0
)
{
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
* @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
'"'
:
case
'\\'
:
case
'/'
:
this
.
out
.
append
(
'\\'
).
append
(
c
);
break
;
case
'\t'
:
this
.
out
.
append
(
"\\t"
);
break
;
case
'\b'
:
this
.
out
.
append
(
"\\b"
);
break
;
case
'\n'
:
this
.
out
.
append
(
"\\n"
);
break
;
case
'\r'
:
this
.
out
.
append
(
"\\r"
);
break
;
case
'\f'
:
this
.
out
.
append
(
"\\f"
);
break
;
default
:
if
(
c
<=
0x1F
)
{
this
.
out
.
append
(
String
.
format
(
"\\u%04x"
,
(
int
)
c
));
}
else
{
this
.
out
.
append
(
c
);
}
break
;
}
}
this
.
out
.
append
(
"\""
);
}
private
void
newline
()
{
if
(
this
.
indent
==
null
)
{
return
;
}
this
.
out
.
append
(
"\n"
);
for
(
int
i
=
0
;
i
<
this
.
stack
.
size
();
i
++)
{
this
.
out
.
append
(
this
.
indent
);
}
}
/**
* Encodes the key (property name) to this stringer.
*
* @param name the name of the forthcoming value. May not be null.
* @return this stringer.
* @throws JSONException if processing of json failed
*/
public
JSONStringer
key
(
String
name
)
throws
JSONException
{
if
(
name
==
null
)
{
throw
new
JSONException
(
"Names must be non-null"
);
}
beforeKey
();
string
(
name
);
return
this
;
}
/**
* Inserts any necessary separators and whitespace before a name. Also
* adjusts the stack to expect the key's value.
* @throws JSONException if processing of json failed
*/
private
void
beforeKey
()
throws
JSONException
{
Scope
context
=
peek
();
if
(
context
==
Scope
.
NONEMPTY_OBJECT
)
{
// first in object
this
.
out
.
append
(
','
);
}
else
if
(
context
!=
Scope
.
EMPTY_OBJECT
)
{
// not in an object!
throw
new
JSONException
(
"Nesting problem"
);
}
newline
();
replaceTop
(
Scope
.
DANGLING_KEY
);
}
/**
* Inserts any necessary separators and whitespace before a literal value,
* inline array, or inline object. Also adjusts the stack to expect either a
* closing bracket or another element.
* @throws JSONException if processing of json failed
*/
private
void
beforeValue
()
throws
JSONException
{
if
(
this
.
stack
.
isEmpty
())
{
return
;
}
Scope
context
=
peek
();
if
(
context
==
Scope
.
EMPTY_ARRAY
)
{
// first in array
replaceTop
(
Scope
.
NONEMPTY_ARRAY
);
newline
();
}
else
if
(
context
==
Scope
.
NONEMPTY_ARRAY
)
{
// another in array
this
.
out
.
append
(
','
);
newline
();
}
else
if
(
context
==
Scope
.
DANGLING_KEY
)
{
// value for key
this
.
out
.
append
(
this
.
indent
==
null
?
":"
:
": "
);
replaceTop
(
Scope
.
NONEMPTY_OBJECT
);
}
else
if
(
context
!=
Scope
.
NULL
)
{
throw
new
JSONException
(
"Nesting problem"
);
}
}
/**
* Returns the encoded JSON string.
*
* <p>If invoked with unterminated arrays or unclosed objects, this method's
* return value is undefined.
*
* <p><strong>Warning:</strong> although it contradicts the general contract
* of {@link Object#toString}, this method returns null if the stringer
* contains no data.
* @return the encoded JSON string.
*/
@Override
public
String
toString
()
{
return
this
.
out
.
length
()
==
0
?
null
:
this
.
out
.
toString
();
}
}
spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/json/JSONTokener.java
View file @
4568f14c
...
...
@@ -61,551 +61,512 @@ package org.springframework.boot.configurationprocessor.json;
*/
public
class
JSONTokener
{
/** The input JSON. */
private
final
String
in
;
/**
* The index of the next character to be returned by {@link #next}. When
* the input is exhausted, this equals the input's length.
*/
private
int
pos
;
/**
* @param in JSON encoded string. Null is not permitted and will yield a
* tokener that throws {@code NullPointerExceptions} when methods are
* called.
*/
public
JSONTokener
(
String
in
)
{
// consume an optional byte order mark (BOM) if it exists
if
(
in
!=
null
&&
in
.
startsWith
(
"\ufeff"
))
{
in
=
in
.
substring
(
1
);
}
this
.
in
=
in
;
}
/**
* Returns the next value from the input.
*
* @return a {@link JSONObject}, {@link JSONArray}, String, Boolean,
* Integer, Long, Double or {@link JSONObject#NULL}.
* @throws JSONException if the input is malformed.
*/
public
Object
nextValue
()
throws
JSONException
{
int
c
=
nextCleanInternal
();
switch
(
c
)
{
case
-
1
:
throw
syntaxError
(
"End of input"
);
case
'{'
:
return
readObject
();
case
'['
:
return
readArray
();
case
'\''
:
case
'"'
:
return
nextString
((
char
)
c
);
default
:
pos
--;
return
readLiteral
();
}
}
private
int
nextCleanInternal
()
throws
JSONException
{
while
(
pos
<
in
.
length
())
{
int
c
=
in
.
charAt
(
pos
++);
switch
(
c
)
{
case
'\t'
:
case
' '
:
case
'\n'
:
case
'\r'
:
continue
;
case
'/'
:
if
(
pos
==
in
.
length
())
{
return
c
;
}
char
peek
=
in
.
charAt
(
pos
);
switch
(
peek
)
{
case
'*'
:
// skip a /* c-style comment */
pos
++;
int
commentEnd
=
in
.
indexOf
(
"*/"
,
pos
);
if
(
commentEnd
==
-
1
)
{
throw
syntaxError
(
"Unterminated comment"
);
}
pos
=
commentEnd
+
2
;
continue
;
case
'/'
:
// skip a // end-of-line comment
pos
++;
skipToEndOfLine
();
continue
;
default
:
return
c
;
}
case
'#'
:
/*
* Skip a # hash end-of-line comment. The JSON RFC doesn't
* specify this behavior, but it's required to parse
* existing documents. See http://b/2571423.
*/
skipToEndOfLine
();
continue
;
default
:
return
c
;
}
}
return
-
1
;
}
/**
* Advances the position until after the next newline character. If the line
* is terminated by "\r\n", the '\n' must be consumed as whitespace by the
* caller.
*/
private
void
skipToEndOfLine
()
{
for
(;
pos
<
in
.
length
();
pos
++)
{
char
c
=
in
.
charAt
(
pos
);
if
(
c
==
'\r'
||
c
==
'\n'
)
{
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 ".
* @throws NumberFormatException if any unicode escape sequences are
* malformed.
*/
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
=
pos
;
while
(
pos
<
in
.
length
())
{
int
c
=
in
.
charAt
(
pos
++);
if
(
c
==
quote
)
{
if
(
builder
==
null
)
{
// a new string avoids leaking memory
return
new
String
(
in
.
substring
(
start
,
pos
-
1
));
}
else
{
builder
.
append
(
in
,
start
,
pos
-
1
);
return
builder
.
toString
();
}
}
if
(
c
==
'\\'
)
{
if
(
pos
==
in
.
length
())
{
throw
syntaxError
(
"Unterminated escape sequence"
);
}
if
(
builder
==
null
)
{
builder
=
new
StringBuilder
();
}
builder
.
append
(
in
,
start
,
pos
-
1
);
builder
.
append
(
readEscapeCharacter
());
start
=
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".
*
* @throws NumberFormatException if any unicode escape sequences are
* malformed.
*/
private
char
readEscapeCharacter
()
throws
JSONException
{
char
escaped
=
in
.
charAt
(
pos
++);
switch
(
escaped
)
{
case
'u'
:
if
(
pos
+
4
>
in
.
length
())
{
throw
syntaxError
(
"Unterminated escape sequence"
);
}
String
hex
=
in
.
substring
(
pos
,
pos
+
4
);
pos
+=
4
;
return
(
char
)
Integer
.
parseInt
(
hex
,
16
);
case
't'
:
return
'\t'
;
case
'b'
:
return
'\b'
;
case
'n'
:
return
'\n'
;
case
'r'
:
return
'\r'
;
case
'f'
:
return
'\f'
;
case
'\''
:
case
'"'
:
case
'\\'
:
default
:
return
escaped
;
}
}
/**
* Reads a null, boolean, numeric or unquoted string literal value. Numeric
* values will be returned as an Integer, Long, or Double, in that order of
* preference.
*/
private
Object
readLiteral
()
throws
JSONException
{
String
literal
=
nextToInternal
(
"{}[]/\\:,=;# \t\f"
);
if
(
literal
.
length
()
==
0
)
{
throw
syntaxError
(
"Expected literal value"
);
}
else
if
(
"null"
.
equalsIgnoreCase
(
literal
))
{
return
JSONObject
.
NULL
;
}
else
if
(
"true"
.
equalsIgnoreCase
(
literal
))
{
return
Boolean
.
TRUE
;
}
else
if
(
"false"
.
equalsIgnoreCase
(
literal
))
{
return
Boolean
.
FALSE
;
}
/* try to parse as an integral type... */
if
(
literal
.
indexOf
(
'.'
)
==
-
1
)
{
int
base
=
10
;
String
number
=
literal
;
if
(
number
.
startsWith
(
"0x"
)
||
number
.
startsWith
(
"0X"
))
{
number
=
number
.
substring
(
2
);
base
=
16
;
}
else
if
(
number
.
startsWith
(
"0"
)
&&
number
.
length
()
>
1
)
{
number
=
number
.
substring
(
1
);
base
=
8
;
}
try
{
long
longValue
=
Long
.
parseLong
(
number
,
base
);
if
(
longValue
<=
Integer
.
MAX_VALUE
&&
longValue
>=
Integer
.
MIN_VALUE
)
{
return
(
int
)
longValue
;
}
else
{
return
longValue
;
}
}
catch
(
NumberFormatException
e
)
{
/*
* This only happens for integral numbers greater than
* Long.MAX_VALUE, numbers in exponential form (5e-10) and
* unquoted strings. Fall through to try floating point.
*/
}
}
/* ...next try to parse as a floating point... */
try
{
return
Double
.
valueOf
(
literal
);
}
catch
(
NumberFormatException
ignored
)
{
}
/* ... finally give up. We have an unquoted string */
return
new
String
(
literal
);
// a new string avoids leaking memory
}
/**
* Returns the string up to but not including any of the given characters or
* a newline character. This does not consume the excluded character.
*/
private
String
nextToInternal
(
String
excluded
)
{
int
start
=
pos
;
for
(;
pos
<
in
.
length
();
pos
++)
{
char
c
=
in
.
charAt
(
pos
);
if
(
c
==
'\r'
||
c
==
'\n'
||
excluded
.
indexOf
(
c
)
!=
-
1
)
{
return
in
.
substring
(
start
,
pos
);
}
}
return
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.
*/
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
)
{
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
(
pos
<
in
.
length
()
&&
in
.
charAt
(
pos
)
==
'>'
)
{
pos
++;
}
result
.
put
((
String
)
name
,
nextValue
());
switch
(
nextCleanInternal
())
{
case
'}'
:
return
result
;
case
';'
:
case
','
:
continue
;
default
:
throw
syntaxError
(
"Unterminated object"
);
}
}
}
/**
* Reads a sequence of values and the trailing closing brace ']' of an
* array. The opening brace '[' should have already been read. Note that
* "[]" yields an empty array, but "[,]" returns a two-element array
* equivalent to "[null,null]".
*/
private
JSONArray
readArray
()
throws
JSONException
{
JSONArray
result
=
new
JSONArray
();
/* to cover input that ends with ",]". */
boolean
hasTrailingSeparator
=
false
;
while
(
true
)
{
switch
(
nextCleanInternal
())
{
case
-
1
:
throw
syntaxError
(
"Unterminated array"
);
case
']'
:
if
(
hasTrailingSeparator
)
{
result
.
put
(
null
);
}
return
result
;
case
','
:
case
';'
:
/* A separator without a value first means "null". */
result
.
put
(
null
);
hasTrailingSeparator
=
true
;
continue
;
default
:
pos
--;
}
result
.
put
(
nextValue
());
switch
(
nextCleanInternal
())
{
case
']'
:
return
result
;
case
','
:
case
';'
:
hasTrailingSeparator
=
true
;
continue
;
default
:
throw
syntaxError
(
"Unterminated array"
);
}
}
}
/**
* Returns an exception containing the given message plus the current
* position and the entire input string.
*/
public
JSONException
syntaxError
(
String
message
)
{
return
new
JSONException
(
message
+
this
);
}
/**
* Returns the current position and the entire input string.
*/
@Override
public
String
toString
()
{
// consistent with the original implementation
return
" at character "
+
pos
+
" of "
+
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.
*/
/**
* Returns true until the input has been exhausted.
*/
public
boolean
more
()
{
return
pos
<
in
.
length
();
}
/**
* Returns the next available character, or the null character '\0' if all
* input has been exhausted. The return value of this method is ambiguous
* for JSON strings that contain the character '\0'.
*/
public
char
next
()
{
return
pos
<
in
.
length
()
?
in
.
charAt
(
pos
++)
:
'\0'
;
}
/**
* Returns the next available character if it equals {@code c}. Otherwise an
* exception is thrown.
*/
public
char
next
(
char
c
)
throws
JSONException
{
char
result
=
next
();
if
(
result
!=
c
)
{
throw
syntaxError
(
"Expected "
+
c
+
" but was "
+
result
);
}
return
result
;
}
/**
* Returns the next character that is not whitespace and does not belong to
* a comment. If the input is exhausted before such a character can be
* found, the null character '\0' is returned. The return value of this
* method is ambiguous for JSON strings that contain the character '\0'.
*/
public
char
nextClean
()
throws
JSONException
{
int
nextCleanInt
=
nextCleanInternal
();
return
nextCleanInt
==
-
1
?
'\0'
:
(
char
)
nextCleanInt
;
}
/**
* Returns the next {@code length} characters of the input.
*
* <p>The returned string shares its backing character array with this
* tokener's input string. If a reference to the returned string may be held
* indefinitely, you should use {@code new String(result)} to copy it first
* to avoid memory leaks.
*
* @throws JSONException if the remaining input is not long enough to
* satisfy this request.
*/
public
String
next
(
int
length
)
throws
JSONException
{
if
(
pos
+
length
>
in
.
length
())
{
throw
syntaxError
(
length
+
" is out of bounds"
);
}
String
result
=
in
.
substring
(
pos
,
pos
+
length
);
pos
+=
length
;
return
result
;
}
/**
* Returns the {@link String#trim trimmed} string holding the characters up
* to but not including the first of:
* <ul>
* <li>any character in {@code excluded}
* <li>a newline character '\n'
* <li>a carriage return '\r'
* </ul>
*
* <p>The returned string shares its backing character array with this
* tokener's input string. If a reference to the returned string may be held
* indefinitely, you should use {@code new String(result)} to copy it first
* to avoid memory leaks.
*
* @return a possibly-empty string
*/
public
String
nextTo
(
String
excluded
)
{
if
(
excluded
==
null
)
{
throw
new
NullPointerException
(
"excluded == null"
);
}
return
nextToInternal
(
excluded
).
trim
();
}
/**
* Equivalent to {@code nextTo(String.valueOf(excluded))}.
*/
public
String
nextTo
(
char
excluded
)
{
return
nextToInternal
(
String
.
valueOf
(
excluded
)).
trim
();
}
/**
* Advances past all input up to and including the next occurrence of
* {@code thru}. If the remaining input doesn't contain {@code thru}, the
* input is exhausted.
*/
public
void
skipPast
(
String
thru
)
{
int
thruStart
=
in
.
indexOf
(
thru
,
pos
);
pos
=
thruStart
==
-
1
?
in
.
length
()
:
(
thruStart
+
thru
.
length
());
}
/**
* Advances past all input up to but not including the next occurrence of
* {@code to}. If the remaining input doesn't contain {@code to}, the input
* is unchanged.
*/
public
char
skipTo
(
char
to
)
{
int
index
=
in
.
indexOf
(
to
,
pos
);
if
(
index
!=
-
1
)
{
pos
=
index
;
return
to
;
}
else
{
return
'\0'
;
}
}
/**
* Unreads the most recent character of input. If no input characters have
* been read, the input is unchanged.
*/
public
void
back
()
{
if
(--
pos
==
-
1
)
{
pos
=
0
;
}
}
/**
* Returns the integer [0..15] value for the given hex character, or -1
* for non-hex input.
*
* @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other
* character will yield a -1 result.
*/
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
;
}
}
/** The input JSON. */
private
final
String
in
;
/**
* The index of the next character to be returned by {@link #next}. When
* the input is exhausted, this equals the input's length.
*/
private
int
pos
;
/**
* @param in JSON encoded string. Null is not permitted and will yield a
* tokener that throws {@code NullPointerExceptions} when methods are
* called.
*/
public
JSONTokener
(
String
in
)
{
// consume an optional byte order mark (BOM) if it exists
if
(
in
!=
null
&&
in
.
startsWith
(
"\ufeff"
))
{
in
=
in
.
substring
(
1
);
}
this
.
in
=
in
;
}
/**
* Returns the next value from the input.
*
* @return a {@link JSONObject}, {@link JSONArray}, String, Boolean,
* Integer, Long, Double or {@link JSONObject#NULL}.
* @throws JSONException if the input is malformed.
*/
public
Object
nextValue
()
throws
JSONException
{
int
c
=
nextCleanInternal
();
switch
(
c
)
{
case
-
1
:
throw
syntaxError
(
"End of input"
);
case
'{'
:
return
readObject
();
case
'['
:
return
readArray
();
case
'\''
:
case
'"'
:
return
nextString
((
char
)
c
);
default
:
this
.
pos
--;
return
readLiteral
();
}
}
private
int
nextCleanInternal
()
throws
JSONException
{
while
(
this
.
pos
<
this
.
in
.
length
())
{
int
c
=
this
.
in
.
charAt
(
this
.
pos
++);
switch
(
c
)
{
case
'\t'
:
case
' '
:
case
'\n'
:
case
'\r'
:
continue
;
case
'/'
:
if
(
this
.
pos
==
this
.
in
.
length
())
{
return
c
;
}
char
peek
=
this
.
in
.
charAt
(
this
.
pos
);
switch
(
peek
)
{
case
'*'
:
// skip a /* c-style comment */
this
.
pos
++;
int
commentEnd
=
this
.
in
.
indexOf
(
"*/"
,
this
.
pos
);
if
(
commentEnd
==
-
1
)
{
throw
syntaxError
(
"Unterminated comment"
);
}
this
.
pos
=
commentEnd
+
2
;
continue
;
case
'/'
:
// skip a // end-of-line comment
this
.
pos
++;
skipToEndOfLine
();
continue
;
default
:
return
c
;
}
case
'#'
:
/*
* Skip a # hash end-of-line comment. The JSON RFC doesn't
* specify this behavior, but it's required to parse
* existing documents. See http://b/2571423.
*/
skipToEndOfLine
();
continue
;
default
:
return
c
;
}
}
return
-
1
;
}
/**
* Advances the position until after the next newline character. If the line
* is terminated by "\r\n", the '\n' must be consumed as whitespace by the
* caller.
*/
private
void
skipToEndOfLine
()
{
for
(;
this
.
pos
<
this
.
in
.
length
();
this
.
pos
++)
{
char
c
=
this
.
in
.
charAt
(
this
.
pos
);
if
(
c
==
'\r'
||
c
==
'\n'
)
{
this
.
pos
++;
break
;
}
}
}
/**
* Returns the string up to but not including {@code quote}, unescaping any
* character escape sequences encountered along the way. The opening quote
* should have already been read. This consumes the closing quote, but does
* not include it in the returned string.
*
* @param quote either ' or ".
* @return the string up to but not including {@code quote}
* @throws NumberFormatException if any unicode escape sequences are
* malformed.
* @throws JSONException if processing of json failed
*/
public
String
nextString
(
char
quote
)
throws
JSONException
{
/*
* For strings that are free of escape sequences, we can just extract
* the result as a substring of the input. But if we encounter an escape
* sequence, we need to use a StringBuilder to compose the result.
*/
StringBuilder
builder
=
null
;
/* the index of the first character not yet appended to the builder. */
int
start
=
this
.
pos
;
while
(
this
.
pos
<
this
.
in
.
length
())
{
int
c
=
this
.
in
.
charAt
(
this
.
pos
++);
if
(
c
==
quote
)
{
if
(
builder
==
null
)
{
// a new string avoids leaking memory
return
new
String
(
this
.
in
.
substring
(
start
,
this
.
pos
-
1
));
}
else
{
builder
.
append
(
this
.
in
,
start
,
this
.
pos
-
1
);
return
builder
.
toString
();
}
}
if
(
c
==
'\\'
)
{
if
(
this
.
pos
==
this
.
in
.
length
())
{
throw
syntaxError
(
"Unterminated escape sequence"
);
}
if
(
builder
==
null
)
{
builder
=
new
StringBuilder
();
}
builder
.
append
(
this
.
in
,
start
,
this
.
pos
-
1
);
builder
.
append
(
readEscapeCharacter
());
start
=
this
.
pos
;
}
}
throw
syntaxError
(
"Unterminated string"
);
}
/**
* Unescapes the character identified by the character or characters that
* immediately follow a backslash. The backslash '\' should have already
* been read. This supports both unicode escapes "u000A" and two-character
* escapes "\n".
*
* @return the unescaped char
* @throws NumberFormatException if any unicode escape sequences are
* malformed.
* @throws JSONException if processing of json failed
*/
private
char
readEscapeCharacter
()
throws
JSONException
{
char
escaped
=
this
.
in
.
charAt
(
this
.
pos
++);
switch
(
escaped
)
{
case
'u'
:
if
(
this
.
pos
+
4
>
this
.
in
.
length
())
{
throw
syntaxError
(
"Unterminated escape sequence"
);
}
String
hex
=
this
.
in
.
substring
(
this
.
pos
,
this
.
pos
+
4
);
this
.
pos
+=
4
;
return
(
char
)
Integer
.
parseInt
(
hex
,
16
);
case
't'
:
return
'\t'
;
case
'b'
:
return
'\b'
;
case
'n'
:
return
'\n'
;
case
'r'
:
return
'\r'
;
case
'f'
:
return
'\f'
;
case
'\''
:
case
'"'
:
case
'\\'
:
default
:
return
escaped
;
}
}
/**
* Reads a null, boolean, numeric or unquoted string literal value. Numeric
* values will be returned as an Integer, Long, or Double, in that order of
* preference.
* @return a literal value
* @throws JSONException if processing of json failed
*/
private
Object
readLiteral
()
throws
JSONException
{
String
literal
=
nextToInternal
(
"{}[]/\\:,=;# \t\f"
);
if
(
literal
.
length
()
==
0
)
{
throw
syntaxError
(
"Expected literal value"
);
}
else
if
(
"null"
.
equalsIgnoreCase
(
literal
))
{
return
JSONObject
.
NULL
;
}
else
if
(
"true"
.
equalsIgnoreCase
(
literal
))
{
return
Boolean
.
TRUE
;
}
else
if
(
"false"
.
equalsIgnoreCase
(
literal
))
{
return
Boolean
.
FALSE
;
}
/* try to parse as an integral type... */
if
(
literal
.
indexOf
(
'.'
)
==
-
1
)
{
int
base
=
10
;
String
number
=
literal
;
if
(
number
.
startsWith
(
"0x"
)
||
number
.
startsWith
(
"0X"
))
{
number
=
number
.
substring
(
2
);
base
=
16
;
}
else
if
(
number
.
startsWith
(
"0"
)
&&
number
.
length
()
>
1
)
{
number
=
number
.
substring
(
1
);
base
=
8
;
}
try
{
long
longValue
=
Long
.
parseLong
(
number
,
base
);
if
(
longValue
<=
Integer
.
MAX_VALUE
&&
longValue
>=
Integer
.
MIN_VALUE
)
{
return
(
int
)
longValue
;
}
else
{
return
longValue
;
}
}
catch
(
NumberFormatException
e
)
{
/*
* This only happens for integral numbers greater than
* Long.MAX_VALUE, numbers in exponential form (5e-10) and
* unquoted strings. Fall through to try floating point.
*/
}
}
/* ...next try to parse as a floating point... */
try
{
return
Double
.
valueOf
(
literal
);
}
catch
(
NumberFormatException
ignored
)
{
}
/* ... finally give up. We have an unquoted string */
return
new
String
(
literal
);
// a new string avoids leaking memory
}
/**
* Returns the string up to but not including any of the given characters or
* a newline character. This does not consume the excluded character.
* @return the string up to but not including any of the given characters or
* a newline character
*/
private
String
nextToInternal
(
String
excluded
)
{
int
start
=
this
.
pos
;
for
(;
this
.
pos
<
this
.
in
.
length
();
this
.
pos
++)
{
char
c
=
this
.
in
.
charAt
(
this
.
pos
);
if
(
c
==
'\r'
||
c
==
'\n'
||
excluded
.
indexOf
(
c
)
!=
-
1
)
{
return
this
.
in
.
substring
(
start
,
this
.
pos
);
}
}
return
this
.
in
.
substring
(
start
);
}
/**
* Reads a sequence of key/value pairs and the trailing closing brace '}' of
* an object. The opening brace '{' should have already been read.
* @return an object
* @throws JSONException if processing of json failed
*/
private
JSONObject
readObject
()
throws
JSONException
{
JSONObject
result
=
new
JSONObject
();
/* Peek to see if this is the empty object. */
int
first
=
nextCleanInternal
();
if
(
first
==
'}'
)
{
return
result
;
}
else
if
(
first
!=
-
1
)
{
this
.
pos
--;
}
while
(
true
)
{
Object
name
=
nextValue
();
if
(!(
name
instanceof
String
))
{
if
(
name
==
null
)
{
throw
syntaxError
(
"Names cannot be null"
);
}
else
{
throw
syntaxError
(
"Names must be strings, but "
+
name
+
" is of type "
+
name
.
getClass
().
getName
());
}
}
/*
* Expect the name/value separator to be either a colon ':', an
* equals sign '=', or an arrow "=>". The last two are bogus but we
* include them because that's what the original implementation did.
*/
int
separator
=
nextCleanInternal
();
if
(
separator
!=
':'
&&
separator
!=
'='
)
{
throw
syntaxError
(
"Expected ':' after "
+
name
);
}
if
(
this
.
pos
<
this
.
in
.
length
()
&&
this
.
in
.
charAt
(
this
.
pos
)
==
'>'
)
{
this
.
pos
++;
}
result
.
put
((
String
)
name
,
nextValue
());
switch
(
nextCleanInternal
())
{
case
'}'
:
return
result
;
case
';'
:
case
','
:
continue
;
default
:
throw
syntaxError
(
"Unterminated object"
);
}
}
}
/**
* Reads a sequence of values and the trailing closing brace ']' of an
* array. The opening brace '[' should have already been read. Note that
* "[]" yields an empty array, but "[,]" returns a two-element array
* equivalent to "[null,null]".
* @return an array
* @throws JSONException if processing of json failed
*/
private
JSONArray
readArray
()
throws
JSONException
{
JSONArray
result
=
new
JSONArray
();
/* to cover input that ends with ",]". */
boolean
hasTrailingSeparator
=
false
;
while
(
true
)
{
switch
(
nextCleanInternal
())
{
case
-
1
:
throw
syntaxError
(
"Unterminated array"
);
case
']'
:
if
(
hasTrailingSeparator
)
{
result
.
put
(
null
);
}
return
result
;
case
','
:
case
';'
:
/* A separator without a value first means "null". */
result
.
put
(
null
);
hasTrailingSeparator
=
true
;
continue
;
default
:
this
.
pos
--;
}
result
.
put
(
nextValue
());
switch
(
nextCleanInternal
())
{
case
']'
:
return
result
;
case
','
:
case
';'
:
hasTrailingSeparator
=
true
;
continue
;
default
:
throw
syntaxError
(
"Unterminated array"
);
}
}
}
/**
* Returns an exception containing the given message plus the current
* position and the entire input string.
* @param message the message
* @return an exception
*/
public
JSONException
syntaxError
(
String
message
)
{
return
new
JSONException
(
message
+
this
);
}
/**
* Returns the current position and the entire input string.
* @return the current position and the entire input string.
*/
@Override
public
String
toString
()
{
// consistent with the original implementation
return
" at character "
+
this
.
pos
+
" of "
+
this
.
in
;
}
/*
* Legacy APIs.
*
* None of the methods below are on the critical path of parsing JSON
* documents. They exist only because they were exposed by the original
* implementation and may be used by some clients.
*/
public
boolean
more
()
{
return
this
.
pos
<
this
.
in
.
length
();
}
public
char
next
()
{
return
this
.
pos
<
this
.
in
.
length
()
?
this
.
in
.
charAt
(
this
.
pos
++)
:
'\0'
;
}
public
char
next
(
char
c
)
throws
JSONException
{
char
result
=
next
();
if
(
result
!=
c
)
{
throw
syntaxError
(
"Expected "
+
c
+
" but was "
+
result
);
}
return
result
;
}
public
char
nextClean
()
throws
JSONException
{
int
nextCleanInt
=
nextCleanInternal
();
return
nextCleanInt
==
-
1
?
'\0'
:
(
char
)
nextCleanInt
;
}
public
String
next
(
int
length
)
throws
JSONException
{
if
(
this
.
pos
+
length
>
this
.
in
.
length
())
{
throw
syntaxError
(
length
+
" is out of bounds"
);
}
String
result
=
this
.
in
.
substring
(
this
.
pos
,
this
.
pos
+
length
);
this
.
pos
+=
length
;
return
result
;
}
public
String
nextTo
(
String
excluded
)
{
if
(
excluded
==
null
)
{
throw
new
NullPointerException
(
"excluded == null"
);
}
return
nextToInternal
(
excluded
).
trim
();
}
public
String
nextTo
(
char
excluded
)
{
return
nextToInternal
(
String
.
valueOf
(
excluded
)).
trim
();
}
public
void
skipPast
(
String
thru
)
{
int
thruStart
=
this
.
in
.
indexOf
(
thru
,
this
.
pos
);
this
.
pos
=
thruStart
==
-
1
?
this
.
in
.
length
()
:
(
thruStart
+
thru
.
length
());
}
public
char
skipTo
(
char
to
)
{
int
index
=
this
.
in
.
indexOf
(
to
,
this
.
pos
);
if
(
index
!=
-
1
)
{
this
.
pos
=
index
;
return
to
;
}
else
{
return
'\0'
;
}
}
public
void
back
()
{
if
(--
this
.
pos
==
-
1
)
{
this
.
pos
=
0
;
}
}
public
static
int
dehexchar
(
char
hex
)
{
if
(
hex
>=
'0'
&&
hex
<=
'9'
)
{
return
hex
-
'0'
;
}
else
if
(
hex
>=
'A'
&&
hex
<=
'F'
)
{
return
hex
-
'A'
+
10
;
}
else
if
(
hex
>=
'a'
&&
hex
<=
'f'
)
{
return
hex
-
'a'
+
10
;
}
else
{
return
-
1
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment