mirror of
https://github.com/fankes/moshi.git
synced 2025-10-18 23:49:21 +08:00
Convert JsonValueReader to Kotlin (#1487)
This commit is contained in:
@@ -1,469 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi;
|
||||
|
||||
import static com.squareup.moshi.JsonScope.CLOSED;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
|
||||
/**
|
||||
* This class reads a JSON document by traversing a Java object comprising maps, lists, and JSON
|
||||
* primitives. It does depth-first traversal keeping a stack starting with the root object. During
|
||||
* traversal a stack tracks the current position in the document:
|
||||
*
|
||||
* <ul>
|
||||
* <li>The next element to act upon is on the top of the stack.
|
||||
* <li>When the top of the stack is a {@link List}, calling {@link #beginArray()} replaces the
|
||||
* list with a {@link JsonIterator}. The first element of the iterator is pushed on top of the
|
||||
* iterator.
|
||||
* <li>Similarly, when the top of the stack is a {@link Map}, calling {@link #beginObject()}
|
||||
* replaces the map with an {@link JsonIterator} of its entries. The first element of the
|
||||
* iterator is pushed on top of the iterator.
|
||||
* <li>When the top of the stack is a {@link Map.Entry}, calling {@link #nextName()} returns the
|
||||
* entry's key and replaces the entry with its value on the stack.
|
||||
* <li>When an element is consumed it is popped. If the new top of the stack has a non-exhausted
|
||||
* iterator, the next element of that iterator is pushed.
|
||||
* <li>If the top of the stack is an exhausted iterator, calling {@link #endArray} or {@link
|
||||
* #endObject} will pop it.
|
||||
* </ul>
|
||||
*/
|
||||
final class JsonValueReader extends JsonReader {
|
||||
/** Sentinel object pushed on {@link #stack} when the reader is closed. */
|
||||
private static final Object JSON_READER_CLOSED = new Object();
|
||||
|
||||
private Object[] stack;
|
||||
|
||||
JsonValueReader(Object root) {
|
||||
scopes[stackSize] = JsonScope.NONEMPTY_DOCUMENT;
|
||||
stack = new Object[32];
|
||||
stack[stackSize++] = root;
|
||||
}
|
||||
|
||||
/** Copy-constructor makes a deep copy for peeking. */
|
||||
JsonValueReader(JsonValueReader copyFrom) {
|
||||
super(copyFrom);
|
||||
|
||||
stack = copyFrom.stack.clone();
|
||||
for (int i = 0; i < stackSize; i++) {
|
||||
if (stack[i] instanceof JsonIterator) {
|
||||
stack[i] = ((JsonIterator) stack[i]).clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginArray() throws IOException {
|
||||
List<?> peeked = require(List.class, Token.BEGIN_ARRAY);
|
||||
|
||||
JsonIterator iterator =
|
||||
new JsonIterator(Token.END_ARRAY, peeked.toArray(new Object[peeked.size()]), 0);
|
||||
stack[stackSize - 1] = iterator;
|
||||
scopes[stackSize - 1] = JsonScope.EMPTY_ARRAY;
|
||||
pathIndices[stackSize - 1] = 0;
|
||||
|
||||
// If the iterator isn't empty push its first value onto the stack.
|
||||
if (iterator.hasNext()) {
|
||||
push(iterator.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endArray() throws IOException {
|
||||
JsonIterator peeked = require(JsonIterator.class, Token.END_ARRAY);
|
||||
if (peeked.endToken != Token.END_ARRAY || peeked.hasNext()) {
|
||||
throw typeMismatch(peeked, Token.END_ARRAY);
|
||||
}
|
||||
remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginObject() throws IOException {
|
||||
Map<?, ?> peeked = require(Map.class, Token.BEGIN_OBJECT);
|
||||
|
||||
JsonIterator iterator =
|
||||
new JsonIterator(Token.END_OBJECT, peeked.entrySet().toArray(new Object[peeked.size()]), 0);
|
||||
stack[stackSize - 1] = iterator;
|
||||
scopes[stackSize - 1] = JsonScope.EMPTY_OBJECT;
|
||||
|
||||
// If the iterator isn't empty push its first value onto the stack.
|
||||
if (iterator.hasNext()) {
|
||||
push(iterator.next());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endObject() throws IOException {
|
||||
JsonIterator peeked = require(JsonIterator.class, Token.END_OBJECT);
|
||||
if (peeked.endToken != Token.END_OBJECT || peeked.hasNext()) {
|
||||
throw typeMismatch(peeked, Token.END_OBJECT);
|
||||
}
|
||||
pathNames[stackSize - 1] = null;
|
||||
remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() throws IOException {
|
||||
if (stackSize == 0) return false;
|
||||
|
||||
Object peeked = stack[stackSize - 1];
|
||||
return !(peeked instanceof Iterator) || ((Iterator) peeked).hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Token peek() throws IOException {
|
||||
if (stackSize == 0) return Token.END_DOCUMENT;
|
||||
|
||||
// If the top of the stack is an iterator, take its first element and push it on the stack.
|
||||
Object peeked = stack[stackSize - 1];
|
||||
if (peeked instanceof JsonIterator) return ((JsonIterator) peeked).endToken;
|
||||
if (peeked instanceof List) return Token.BEGIN_ARRAY;
|
||||
if (peeked instanceof Map) return Token.BEGIN_OBJECT;
|
||||
if (peeked instanceof Map.Entry) return Token.NAME;
|
||||
if (peeked instanceof String) return Token.STRING;
|
||||
if (peeked instanceof Boolean) return Token.BOOLEAN;
|
||||
if (peeked instanceof Number) return Token.NUMBER;
|
||||
if (peeked == null) return Token.NULL;
|
||||
if (peeked == JSON_READER_CLOSED) throw new IllegalStateException("JsonReader is closed");
|
||||
|
||||
throw typeMismatch(peeked, "a JSON value");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextName() throws IOException {
|
||||
Map.Entry<?, ?> peeked = require(Map.Entry.class, Token.NAME);
|
||||
|
||||
// Swap the Map.Entry for its value on the stack and return its key.
|
||||
String result = stringKey(peeked);
|
||||
stack[stackSize - 1] = peeked.getValue();
|
||||
pathNames[stackSize - 2] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int selectName(Options options) throws IOException {
|
||||
Map.Entry<?, ?> peeked = require(Map.Entry.class, Token.NAME);
|
||||
String name = stringKey(peeked);
|
||||
for (int i = 0, length = options.strings.length; i < length; i++) {
|
||||
// Swap the Map.Entry for its value on the stack and return its key.
|
||||
if (options.strings[i].equals(name)) {
|
||||
stack[stackSize - 1] = peeked.getValue();
|
||||
pathNames[stackSize - 2] = name;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipName() throws IOException {
|
||||
if (failOnUnknown) {
|
||||
// Capture the peeked value before nextName() since it will reset its value.
|
||||
Token peeked = peek();
|
||||
nextName(); // Move the path forward onto the offending name.
|
||||
throw new JsonDataException("Cannot skip unexpected " + peeked + " at " + getPath());
|
||||
}
|
||||
|
||||
Map.Entry<?, ?> peeked = require(Map.Entry.class, Token.NAME);
|
||||
|
||||
// Swap the Map.Entry for its value on the stack.
|
||||
stack[stackSize - 1] = peeked.getValue();
|
||||
pathNames[stackSize - 2] = "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nextString() throws IOException {
|
||||
Object peeked = (stackSize != 0 ? stack[stackSize - 1] : null);
|
||||
if (peeked instanceof String) {
|
||||
remove();
|
||||
return (String) peeked;
|
||||
}
|
||||
if (peeked instanceof Number) {
|
||||
remove();
|
||||
return peeked.toString();
|
||||
}
|
||||
if (peeked == JSON_READER_CLOSED) {
|
||||
throw new IllegalStateException("JsonReader is closed");
|
||||
}
|
||||
throw typeMismatch(peeked, Token.STRING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int selectString(Options options) throws IOException {
|
||||
Object peeked = (stackSize != 0 ? stack[stackSize - 1] : null);
|
||||
|
||||
if (!(peeked instanceof String)) {
|
||||
if (peeked == JSON_READER_CLOSED) {
|
||||
throw new IllegalStateException("JsonReader is closed");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
String peekedString = (String) peeked;
|
||||
|
||||
for (int i = 0, length = options.strings.length; i < length; i++) {
|
||||
if (options.strings[i].equals(peekedString)) {
|
||||
remove();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean nextBoolean() throws IOException {
|
||||
Boolean peeked = require(Boolean.class, Token.BOOLEAN);
|
||||
remove();
|
||||
return peeked;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T> T nextNull() throws IOException {
|
||||
require(Void.class, Token.NULL);
|
||||
remove();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double nextDouble() throws IOException {
|
||||
Object peeked = require(Object.class, Token.NUMBER);
|
||||
|
||||
double result;
|
||||
if (peeked instanceof Number) {
|
||||
result = ((Number) peeked).doubleValue();
|
||||
} else if (peeked instanceof String) {
|
||||
try {
|
||||
result = Double.parseDouble((String) peeked);
|
||||
} catch (NumberFormatException e) {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
} else {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
|
||||
throw new JsonEncodingException(
|
||||
"JSON forbids NaN and infinities: " + result + " at path " + getPath());
|
||||
}
|
||||
remove();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextLong() throws IOException {
|
||||
Object peeked = require(Object.class, Token.NUMBER);
|
||||
|
||||
long result;
|
||||
if (peeked instanceof Number) {
|
||||
result = ((Number) peeked).longValue();
|
||||
} else if (peeked instanceof String) {
|
||||
try {
|
||||
result = Long.parseLong((String) peeked);
|
||||
} catch (NumberFormatException e) {
|
||||
try {
|
||||
BigDecimal asDecimal = new BigDecimal((String) peeked);
|
||||
result = asDecimal.longValueExact();
|
||||
} catch (NumberFormatException e2) {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
remove();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() throws IOException {
|
||||
Object peeked = require(Object.class, Token.NUMBER);
|
||||
|
||||
int result;
|
||||
if (peeked instanceof Number) {
|
||||
result = ((Number) peeked).intValue();
|
||||
} else if (peeked instanceof String) {
|
||||
try {
|
||||
result = Integer.parseInt((String) peeked);
|
||||
} catch (NumberFormatException e) {
|
||||
try {
|
||||
BigDecimal asDecimal = new BigDecimal((String) peeked);
|
||||
result = asDecimal.intValueExact();
|
||||
} catch (NumberFormatException e2) {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw typeMismatch(peeked, Token.NUMBER);
|
||||
}
|
||||
remove();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipValue() throws IOException {
|
||||
if (failOnUnknown) {
|
||||
throw new JsonDataException("Cannot skip unexpected " + peek() + " at " + getPath());
|
||||
}
|
||||
|
||||
// If this element is in an object clear out the key.
|
||||
if (stackSize > 1) {
|
||||
pathNames[stackSize - 2] = "null";
|
||||
}
|
||||
|
||||
Object skipped = stackSize != 0 ? stack[stackSize - 1] : null;
|
||||
|
||||
if (skipped instanceof JsonIterator) {
|
||||
throw new JsonDataException("Expected a value but was " + peek() + " at path " + getPath());
|
||||
}
|
||||
if (skipped instanceof Map.Entry) {
|
||||
// We're skipping a name. Promote the map entry's value.
|
||||
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) stack[stackSize - 1];
|
||||
stack[stackSize - 1] = entry.getValue();
|
||||
} else if (stackSize > 0) {
|
||||
// We're skipping a value.
|
||||
remove();
|
||||
} else {
|
||||
throw new JsonDataException("Expected a value but was " + peek() + " at path " + getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedSource nextSource() throws IOException {
|
||||
Object value = readJsonValue();
|
||||
Buffer result = new Buffer();
|
||||
try (JsonWriter jsonWriter = JsonWriter.of(result)) {
|
||||
jsonWriter.jsonValue(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonReader peekJson() {
|
||||
return new JsonValueReader(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void promoteNameToValue() throws IOException {
|
||||
if (hasNext()) {
|
||||
String name = nextName();
|
||||
push(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
Arrays.fill(stack, 0, stackSize, null);
|
||||
stack[0] = JSON_READER_CLOSED;
|
||||
scopes[0] = CLOSED;
|
||||
stackSize = 1;
|
||||
}
|
||||
|
||||
private void push(Object newTop) {
|
||||
if (stackSize == stack.length) {
|
||||
if (stackSize == 256) {
|
||||
throw new JsonDataException("Nesting too deep at " + getPath());
|
||||
}
|
||||
scopes = Arrays.copyOf(scopes, scopes.length * 2);
|
||||
pathNames = Arrays.copyOf(pathNames, pathNames.length * 2);
|
||||
pathIndices = Arrays.copyOf(pathIndices, pathIndices.length * 2);
|
||||
stack = Arrays.copyOf(stack, stack.length * 2);
|
||||
}
|
||||
stack[stackSize++] = newTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the top of the stack which is required to be a {@code type}. Throws if this reader is
|
||||
* closed, or if the type isn't what was expected.
|
||||
*/
|
||||
private @Nullable <T> T require(Class<T> type, Token expected) throws IOException {
|
||||
Object peeked = (stackSize != 0 ? stack[stackSize - 1] : null);
|
||||
|
||||
if (type.isInstance(peeked)) {
|
||||
return type.cast(peeked);
|
||||
}
|
||||
if (peeked == null && expected == Token.NULL) {
|
||||
return null;
|
||||
}
|
||||
if (peeked == JSON_READER_CLOSED) {
|
||||
throw new IllegalStateException("JsonReader is closed");
|
||||
}
|
||||
throw typeMismatch(peeked, expected);
|
||||
}
|
||||
|
||||
private String stringKey(Map.Entry<?, ?> entry) {
|
||||
Object name = entry.getKey();
|
||||
if (name instanceof String) return (String) name;
|
||||
throw typeMismatch(name, Token.NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value and prepares for the next. If we're iterating a map or list this advances the
|
||||
* iterator.
|
||||
*/
|
||||
private void remove() {
|
||||
stackSize--;
|
||||
stack[stackSize] = null;
|
||||
scopes[stackSize] = 0;
|
||||
|
||||
// If we're iterating an array or an object push its next element on to the stack.
|
||||
if (stackSize > 0) {
|
||||
pathIndices[stackSize - 1]++;
|
||||
|
||||
Object parent = stack[stackSize - 1];
|
||||
if (parent instanceof Iterator && ((Iterator<?>) parent).hasNext()) {
|
||||
push(((Iterator<?>) parent).next());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class JsonIterator implements Iterator<Object>, Cloneable {
|
||||
final Token endToken;
|
||||
final Object[] array;
|
||||
int next;
|
||||
|
||||
JsonIterator(Token endToken, Object[] array, int next) {
|
||||
this.endToken = endToken;
|
||||
this.array = array;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next < array.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
return array[next++];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonIterator clone() {
|
||||
// No need to copy the array; it's read-only.
|
||||
return new JsonIterator(endToken, array, next);
|
||||
}
|
||||
}
|
||||
}
|
393
moshi/src/main/java/com/squareup/moshi/JsonValueReader.kt
Normal file
393
moshi/src/main/java/com/squareup/moshi/JsonValueReader.kt
Normal file
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Square, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.squareup.moshi
|
||||
|
||||
import com.squareup.moshi.JsonValueReader.JsonIterator
|
||||
import com.squareup.moshi.internal.knownNotNull
|
||||
import okio.Buffer
|
||||
import okio.BufferedSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Sentinel object pushed on [JsonValueReader.stack] when the reader is closed. */
|
||||
private val JSON_READER_CLOSED = Any()
|
||||
|
||||
/**
|
||||
* This class reads a JSON document by traversing a Java object comprising maps, lists, and JSON
|
||||
* primitives. It does depth-first traversal keeping a stack starting with the root object. During
|
||||
* traversal a stack tracks the current position in the document:
|
||||
* * The next element to act upon is on the top of the stack.
|
||||
* * When the top of the stack is a [List], calling [beginArray] replaces the list with a [JsonIterator]. The first
|
||||
* element of the iterator is pushed on top of the iterator.
|
||||
* * Similarly, when the top of the stack is a [Map], calling [beginObject] replaces the map with an [JsonIterator]
|
||||
* of its entries. The first element of the iterator is pushed on top of the iterator.
|
||||
* * When the top of the stack is a [Map.Entry], calling [nextName] returns the entry's key and replaces the entry
|
||||
* with its value on the stack.
|
||||
* * When an element is consumed it is popped. If the new top of the stack has a non-exhausted iterator, the next
|
||||
* element of that iterator is pushed.
|
||||
* * If the top of the stack is an exhausted iterator, calling [endArray] or [endObject] will pop it.
|
||||
*/
|
||||
internal class JsonValueReader : JsonReader {
|
||||
private var stack: Array<Any?>
|
||||
|
||||
constructor(root: Any?) {
|
||||
scopes[stackSize] = JsonScope.NONEMPTY_DOCUMENT
|
||||
stack = arrayOfNulls(32)
|
||||
stack[stackSize++] = root
|
||||
}
|
||||
|
||||
/** Copy-constructor makes a deep copy for peeking. */
|
||||
constructor(copyFrom: JsonValueReader) : super(copyFrom) {
|
||||
stack = copyFrom.stack.clone()
|
||||
for (i in 0 until stackSize) {
|
||||
val element = stack[i]
|
||||
if (element is JsonIterator) {
|
||||
stack[i] = element.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun beginArray() {
|
||||
val peeked = require<List<*>>(Token.BEGIN_ARRAY)
|
||||
val iterator = JsonIterator(Token.END_ARRAY, peeked.toTypedArray(), 0)
|
||||
stack[stackSize - 1] = iterator
|
||||
scopes[stackSize - 1] = JsonScope.EMPTY_ARRAY
|
||||
pathIndices[stackSize - 1] = 0
|
||||
|
||||
// If the iterator isn't empty push its first value onto the stack.
|
||||
if (iterator.hasNext()) {
|
||||
push(iterator.next())
|
||||
}
|
||||
}
|
||||
|
||||
override fun endArray() {
|
||||
val peeked = require<JsonIterator>(Token.END_ARRAY)
|
||||
if (peeked.endToken != Token.END_ARRAY || peeked.hasNext()) {
|
||||
throw typeMismatch(peeked, Token.END_ARRAY)
|
||||
}
|
||||
remove()
|
||||
}
|
||||
|
||||
override fun beginObject() {
|
||||
val peeked = require<Map<*, *>>(Token.BEGIN_OBJECT)
|
||||
val iterator = JsonIterator(Token.END_OBJECT, peeked.entries.toTypedArray(), 0)
|
||||
stack[stackSize - 1] = iterator
|
||||
scopes[stackSize - 1] = JsonScope.EMPTY_OBJECT
|
||||
|
||||
// If the iterator isn't empty push its first value onto the stack.
|
||||
if (iterator.hasNext()) {
|
||||
push(iterator.next())
|
||||
}
|
||||
}
|
||||
|
||||
override fun endObject() {
|
||||
val peeked = require<JsonIterator>(Token.END_OBJECT)
|
||||
if (peeked.endToken != Token.END_OBJECT || peeked.hasNext()) {
|
||||
throw typeMismatch(peeked, Token.END_OBJECT)
|
||||
}
|
||||
pathNames[stackSize - 1] = null
|
||||
remove()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
if (stackSize == 0) return false
|
||||
val peeked = stack[stackSize - 1]
|
||||
return peeked !is Iterator<*> || peeked.hasNext()
|
||||
}
|
||||
|
||||
override fun peek(): Token {
|
||||
if (stackSize == 0) return Token.END_DOCUMENT
|
||||
|
||||
// If the top of the stack is an iterator, take its first element and push it on the stack.
|
||||
return when (val peeked = stack[stackSize - 1]) {
|
||||
is JsonIterator -> peeked.endToken
|
||||
is List<*> -> Token.BEGIN_ARRAY
|
||||
is Map<*, *> -> Token.BEGIN_OBJECT
|
||||
is Map.Entry<*, *> -> Token.NAME
|
||||
is String -> Token.STRING
|
||||
is Boolean -> Token.BOOLEAN
|
||||
is Number -> Token.NUMBER
|
||||
null -> Token.NULL
|
||||
else -> ifNotClosed(peeked) {
|
||||
throw typeMismatch(peeked, "a JSON value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextName(): String {
|
||||
val peeked = require<Map.Entry<*, *>>(Token.NAME)
|
||||
|
||||
// Swap the Map.Entry for its value on the stack and return its key.
|
||||
val result = stringKey(peeked)
|
||||
stack[stackSize - 1] = peeked.value
|
||||
pathNames[stackSize - 2] = result
|
||||
return result
|
||||
}
|
||||
|
||||
override fun selectName(options: Options): Int {
|
||||
val peeked = require<Map.Entry<*, *>>(Token.NAME)
|
||||
val name = stringKey(peeked)
|
||||
for (i in options.strings.indices) {
|
||||
// Swap the Map.Entry for its value on the stack and return its key.
|
||||
if (options.strings[i] == name) {
|
||||
stack[stackSize - 1] = peeked.value
|
||||
pathNames[stackSize - 2] = name
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun skipName() {
|
||||
if (failOnUnknown) {
|
||||
// Capture the peeked value before nextName() since it will reset its value.
|
||||
val peeked = peek()
|
||||
nextName() // Move the path forward onto the offending name.
|
||||
throw JsonDataException("Cannot skip unexpected $peeked at $path")
|
||||
}
|
||||
val (_, value) = require<Map.Entry<*, *>>(Token.NAME)
|
||||
|
||||
// Swap the Map.Entry for its value on the stack.
|
||||
stack[stackSize - 1] = value
|
||||
pathNames[stackSize - 2] = "null"
|
||||
}
|
||||
|
||||
override fun nextString(): String {
|
||||
return when (val peeked = if (stackSize != 0) stack[stackSize - 1] else null) {
|
||||
is String -> {
|
||||
remove()
|
||||
peeked
|
||||
}
|
||||
is Number -> {
|
||||
remove()
|
||||
peeked.toString()
|
||||
}
|
||||
else -> ifNotClosed(peeked) {
|
||||
throw typeMismatch(peeked, Token.STRING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun selectString(options: Options): Int {
|
||||
val peeked = if (stackSize != 0) stack[stackSize - 1] else null
|
||||
if (peeked !is String) {
|
||||
ifNotClosed(peeked) {
|
||||
-1
|
||||
}
|
||||
}
|
||||
for (i in options.strings.indices) {
|
||||
if (options.strings[i] == peeked) {
|
||||
remove()
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
override fun nextBoolean(): Boolean {
|
||||
val peeked = require<Boolean>(Token.BOOLEAN)
|
||||
remove()
|
||||
return peeked
|
||||
}
|
||||
|
||||
override fun <T> nextNull(): T? {
|
||||
requireNull()
|
||||
remove()
|
||||
return null
|
||||
}
|
||||
|
||||
override fun nextDouble(): Double {
|
||||
val result = when (val peeked = require<Any>(Token.NUMBER)) {
|
||||
is Number -> peeked.toDouble()
|
||||
is String -> {
|
||||
try {
|
||||
peeked.toDouble()
|
||||
} catch (e: NumberFormatException) {
|
||||
throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
}
|
||||
if (!lenient && (result.isNaN() || result.isInfinite())) {
|
||||
throw JsonEncodingException("JSON forbids NaN and infinities: $result at path $path")
|
||||
}
|
||||
remove()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun nextLong(): Long {
|
||||
val result: Long = when (val peeked = require<Any>(Token.NUMBER)) {
|
||||
is Number -> peeked.toLong()
|
||||
is String -> try {
|
||||
peeked.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
try {
|
||||
BigDecimal(peeked).longValueExact()
|
||||
} catch (e2: NumberFormatException) {
|
||||
throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
}
|
||||
else -> throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
remove()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun nextInt(): Int {
|
||||
val result = when (val peeked = require<Any>(Token.NUMBER)) {
|
||||
is Number -> peeked.toInt()
|
||||
is String -> try {
|
||||
peeked.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
try {
|
||||
BigDecimal(peeked).intValueExact()
|
||||
} catch (e2: NumberFormatException) {
|
||||
throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
}
|
||||
else -> throw typeMismatch(peeked, Token.NUMBER)
|
||||
}
|
||||
remove()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun skipValue() {
|
||||
if (failOnUnknown) {
|
||||
throw JsonDataException("Cannot skip unexpected ${peek()} at $path")
|
||||
}
|
||||
|
||||
// If this element is in an object clear out the key.
|
||||
if (stackSize > 1) {
|
||||
pathNames[stackSize - 2] = "null"
|
||||
}
|
||||
|
||||
val skipped = if (stackSize != 0) stack[stackSize - 1] else null
|
||||
if (skipped is JsonIterator) {
|
||||
throw JsonDataException("Expected a value but was ${peek()} at path $path")
|
||||
}
|
||||
if (skipped is Map.Entry<*, *>) {
|
||||
// We're skipping a name. Promote the map entry's value.
|
||||
val entry = stack[stackSize - 1] as Map.Entry<*, *>
|
||||
stack[stackSize - 1] = entry.value
|
||||
} else if (stackSize > 0) {
|
||||
// We're skipping a value.
|
||||
remove()
|
||||
} else {
|
||||
throw JsonDataException("Expected a value but was ${peek()} at path $path")
|
||||
}
|
||||
}
|
||||
|
||||
override fun nextSource(): BufferedSource {
|
||||
val value = readJsonValue()
|
||||
val result = Buffer()
|
||||
JsonWriter.of(result).use { jsonWriter -> jsonWriter.jsonValue(value) }
|
||||
return result
|
||||
}
|
||||
|
||||
override fun peekJson(): JsonReader = JsonValueReader(this)
|
||||
|
||||
override fun promoteNameToValue() {
|
||||
if (hasNext()) {
|
||||
val name = nextName()
|
||||
push(name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
stack.fill(null, 0, stackSize)
|
||||
stack[0] = JSON_READER_CLOSED
|
||||
scopes[0] = JsonScope.CLOSED
|
||||
stackSize = 1
|
||||
}
|
||||
|
||||
private fun push(newTop: Any?) {
|
||||
if (stackSize == stack.size) {
|
||||
if (stackSize == 256) {
|
||||
throw JsonDataException("Nesting too deep at $path")
|
||||
}
|
||||
scopes = scopes.copyOf(scopes.size * 2)
|
||||
pathNames = pathNames.copyOf(pathNames.size * 2)
|
||||
pathIndices = pathIndices.copyOf(pathIndices.size * 2)
|
||||
stack = stack.copyOf(stack.size * 2)
|
||||
}
|
||||
stack[stackSize++] = newTop
|
||||
}
|
||||
|
||||
private inline fun <reified T> require(expected: Token): T = knownNotNull(require(T::class.java, expected))
|
||||
|
||||
private fun requireNull() = require(Void::class.java, Token.NULL)
|
||||
|
||||
/**
|
||||
* Returns the top of the stack which is required to be a `type`. Throws if this reader is
|
||||
* closed, or if the type isn't what was expected.
|
||||
*/
|
||||
private fun <T> require(type: Class<T>, expected: Token): T? {
|
||||
val peeked = if (stackSize != 0) stack[stackSize - 1] else null
|
||||
if (type.isInstance(peeked)) {
|
||||
return type.cast(peeked)
|
||||
}
|
||||
if (peeked == null && expected == Token.NULL) {
|
||||
return null
|
||||
}
|
||||
ifNotClosed(peeked) {
|
||||
throw typeMismatch(peeked, expected)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stringKey(entry: Map.Entry<*, *>): String {
|
||||
val name = entry.key
|
||||
if (name is String) return name
|
||||
throw typeMismatch(name, Token.NAME)
|
||||
}
|
||||
|
||||
private inline fun <T> ifNotClosed(peeked: Any?, body: () -> T): T {
|
||||
check(peeked !== JSON_READER_CLOSED) { "JsonReader is closed" }
|
||||
return body()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a value and prepares for the next. If we're iterating a map or list this advances the
|
||||
* iterator.
|
||||
*/
|
||||
private fun remove() {
|
||||
stackSize--
|
||||
stack[stackSize] = null
|
||||
scopes[stackSize] = 0
|
||||
|
||||
// If we're iterating an array or an object push its next element on to the stack.
|
||||
if (stackSize > 0) {
|
||||
pathIndices[stackSize - 1]++
|
||||
val parent = stack[stackSize - 1]
|
||||
if (parent is Iterator<*> && parent.hasNext()) {
|
||||
push(parent.next())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class JsonIterator(
|
||||
val endToken: Token,
|
||||
val array: Array<Any?>,
|
||||
var next: Int
|
||||
) : Iterator<Any?>, Cloneable {
|
||||
override fun hasNext() = next < array.size
|
||||
|
||||
override fun next() = array[next++]
|
||||
|
||||
// No need to copy the array; it's read-only.
|
||||
public override fun clone() = JsonIterator(endToken, array, next)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user