Promote stack management to the JsonReader/JsonWriter supertypes.

It turns out that we can reuse a lot of code with inheritance. Who knew?
This commit is contained in:
jwilson
2017-01-24 21:50:19 -05:00
parent a90b6c7740
commit 4b7ced70e4
7 changed files with 184 additions and 276 deletions

View File

@@ -57,35 +57,11 @@ final class BufferedSinkJsonWriter extends JsonWriter {
/** The output data, containing at most one top-level array or object. */
private final BufferedSink sink;
// The nesting stack. Using a manual array rather than an ArrayList saves 20%. This stack permits
// up to 32 levels of nesting including the top-level document. Deeper nesting is prone to trigger
// StackOverflowErrors.
private final int[] stack = new int[32];
private int stackSize = 0;
{
push(EMPTY_DOCUMENT);
}
private final String[] pathNames = new String[32];
private final int[] pathIndices = new int[32];
/**
* A string containing a full set of spaces for a single level of
* indentation, or null for no pretty printing.
*/
private String indent;
/**
* The name/value separator; either ":" or ": ".
*/
/** The name/value separator; either ":" or ": ". */
private String separator = ":";
private boolean lenient;
private String deferredName;
private boolean serializeNulls;
private boolean promoteNameToValue;
BufferedSinkJsonWriter(BufferedSink sink) {
@@ -93,36 +69,12 @@ final class BufferedSinkJsonWriter extends JsonWriter {
throw new NullPointerException("sink == null");
}
this.sink = sink;
pushScope(EMPTY_DOCUMENT);
}
@Override public final void setIndent(String indent) {
if (indent.length() == 0) {
this.indent = null;
this.separator = ":";
} else {
this.indent = indent;
this.separator = ": ";
}
}
@Override public final String getIndent() {
return indent != null ? indent : "";
}
@Override public final void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public final void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls;
}
@Override public final boolean getSerializeNulls() {
return serializeNulls;
@Override public void setIndent(String indent) {
super.setIndent(indent);
this.separator = !indent.isEmpty() ? ": " : ":";
}
@Override public JsonWriter beginArray() throws IOException {
@@ -150,7 +102,7 @@ final class BufferedSinkJsonWriter extends JsonWriter {
*/
private JsonWriter open(int empty, String openBracket) throws IOException {
beforeValue();
push(empty);
pushScope(empty);
pathIndices[stackSize - 1] = 0;
sink.writeUtf8(openBracket);
return this;
@@ -160,9 +112,8 @@ final class BufferedSinkJsonWriter extends JsonWriter {
* Closes the current scope by appending any necessary whitespace and the
* given bracket.
*/
private JsonWriter close(int empty, int nonempty, String closeBracket)
throws IOException {
int context = peek();
private JsonWriter close(int empty, int nonempty, String closeBracket) throws IOException {
int context = peekScope();
if (context != nonempty && context != empty) {
throw new IllegalStateException("Nesting problem.");
}
@@ -180,30 +131,6 @@ final class BufferedSinkJsonWriter extends JsonWriter {
return this;
}
private void push(int newTop) {
if (stackSize == stack.length) {
throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?");
}
stack[stackSize++] = newTop;
}
/**
* Returns the scope on the top of the stack.
*/
private int peek() {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
return stack[stackSize - 1];
}
/**
* Replace the value on the top of the stack with the given value.
*/
private void replaceTop(int topOfStack) {
stack[stackSize - 1] = topOfStack;
}
@Override public JsonWriter name(String name) throws IOException {
if (name == null) {
throw new NullPointerException("name == null");
@@ -337,7 +264,7 @@ final class BufferedSinkJsonWriter extends JsonWriter {
sink.close();
int size = stackSize;
if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) {
if (size > 1 || size == 1 && scopes[size - 1] != NONEMPTY_DOCUMENT) {
throw new IOException("Incomplete document");
}
stackSize = 0;
@@ -395,7 +322,7 @@ final class BufferedSinkJsonWriter extends JsonWriter {
* adjusts the stack to expect the name's value.
*/
private void beforeName() throws IOException {
int context = peek();
int context = peekScope();
if (context == NONEMPTY_OBJECT) { // first in object
sink.writeByte(',');
} else if (context != EMPTY_OBJECT) { // not in an object!
@@ -412,7 +339,7 @@ final class BufferedSinkJsonWriter extends JsonWriter {
*/
@SuppressWarnings("fallthrough")
private void beforeValue() throws IOException {
switch (peek()) {
switch (peekScope()) {
case NONEMPTY_DOCUMENT:
if (!lenient) {
throw new IllegalStateException(
@@ -444,14 +371,10 @@ final class BufferedSinkJsonWriter extends JsonWriter {
}
@Override void promoteNameToValue() throws IOException {
int context = peek();
int context = peekScope();
if (context != NONEMPTY_OBJECT && context != EMPTY_OBJECT) {
throw new IllegalStateException("Nesting problem.");
}
promoteNameToValue = true;
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
}

View File

@@ -63,12 +63,6 @@ final class BufferedSourceJsonReader extends JsonReader {
private static final int NUMBER_CHAR_EXP_SIGN = 6;
private static final int NUMBER_CHAR_EXP_DIGIT = 7;
/** True to accept non-spec compliant JSON */
private boolean lenient = false;
/** True to throw a {@link JsonDataException} on any attempt to call {@link #skipValue()}. */
private boolean failOnUnknown = false;
/** The input JSON. */
private final BufferedSource source;
private final Buffer buffer;
@@ -94,41 +88,13 @@ final class BufferedSourceJsonReader extends JsonReader {
*/
private String peekedString;
// The nesting stack. Using a manual array rather than an ArrayList saves 20%. This stack permits
// up to 32 levels of nesting including the top-level document. Deeper nesting is prone to trigger
// StackOverflowErrors.
private final int[] stack = new int[32];
private int stackSize = 0;
{
stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;
}
private final String[] pathNames = new String[32];
private final int[] pathIndices = new int[32];
BufferedSourceJsonReader(BufferedSource source) {
if (source == null) {
throw new NullPointerException("source == null");
}
this.source = source;
this.buffer = source.buffer();
}
@Override public void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public void setFailOnUnknown(boolean failOnUnknown) {
this.failOnUnknown = failOnUnknown;
}
@Override public boolean failOnUnknown() {
return failOnUnknown;
pushScope(JsonScope.EMPTY_DOCUMENT);
}
@Override public void beginArray() throws IOException {
@@ -137,7 +103,7 @@ final class BufferedSourceJsonReader extends JsonReader {
p = doPeek();
}
if (p == PEEKED_BEGIN_ARRAY) {
push(JsonScope.EMPTY_ARRAY);
pushScope(JsonScope.EMPTY_ARRAY);
pathIndices[stackSize - 1] = 0;
peeked = PEEKED_NONE;
} else {
@@ -167,7 +133,7 @@ final class BufferedSourceJsonReader extends JsonReader {
p = doPeek();
}
if (p == PEEKED_BEGIN_OBJECT) {
push(JsonScope.EMPTY_OBJECT);
pushScope(JsonScope.EMPTY_OBJECT);
peeked = PEEKED_NONE;
} else {
throw new JsonDataException("Expected BEGIN_OBJECT but was " + peek()
@@ -240,9 +206,9 @@ final class BufferedSourceJsonReader extends JsonReader {
}
private int doPeek() throws IOException {
int peekStack = stack[stackSize - 1];
int peekStack = scopes[stackSize - 1];
if (peekStack == JsonScope.EMPTY_ARRAY) {
stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
scopes[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
} else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
// Look for a comma before the next element.
int c = nextNonWhitespace(true);
@@ -258,7 +224,7 @@ final class BufferedSourceJsonReader extends JsonReader {
throw syntaxError("Unterminated array");
}
} else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
stack[stackSize - 1] = JsonScope.DANGLING_NAME;
scopes[stackSize - 1] = JsonScope.DANGLING_NAME;
// Look for a comma before the next element.
if (peekStack == JsonScope.NONEMPTY_OBJECT) {
int c = nextNonWhitespace(true);
@@ -299,7 +265,7 @@ final class BufferedSourceJsonReader extends JsonReader {
}
}
} else if (peekStack == JsonScope.DANGLING_NAME) {
stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
scopes[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
// Look for a colon before the value.
int c = nextNonWhitespace(true);
buffer.readByte(); // Consume ':'.
@@ -316,7 +282,7 @@ final class BufferedSourceJsonReader extends JsonReader {
throw syntaxError("Expected ':'");
}
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
scopes[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
} else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
int c = nextNonWhitespace(false);
if (c == -1) {
@@ -923,7 +889,7 @@ final class BufferedSourceJsonReader extends JsonReader {
@Override public void close() throws IOException {
peeked = PEEKED_NONE;
stack[0] = JsonScope.CLOSED;
scopes[0] = JsonScope.CLOSED;
stackSize = 1;
buffer.clear();
source.close();
@@ -941,10 +907,10 @@ final class BufferedSourceJsonReader extends JsonReader {
}
if (p == PEEKED_BEGIN_ARRAY) {
push(JsonScope.EMPTY_ARRAY);
pushScope(JsonScope.EMPTY_ARRAY);
count++;
} else if (p == PEEKED_BEGIN_OBJECT) {
push(JsonScope.EMPTY_OBJECT);
pushScope(JsonScope.EMPTY_OBJECT);
count++;
} else if (p == PEEKED_END_ARRAY) {
stackSize--;
@@ -968,13 +934,6 @@ final class BufferedSourceJsonReader extends JsonReader {
pathNames[stackSize - 1] = "null";
}
private void push(int newTop) {
if (stackSize == stack.length) {
throw new JsonDataException("Nesting too deep at " + getPath());
}
stack[stackSize++] = newTop;
}
/**
* Returns the next character in the stream that is neither whitespace nor a
* part of a comment. When this returns, the returned character is always at
@@ -1083,10 +1042,6 @@ final class BufferedSourceJsonReader extends JsonReader {
return "JsonReader(" + source + ")";
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, stack, pathNames, pathIndices);
}
/**
* 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
@@ -1151,14 +1106,6 @@ final class BufferedSourceJsonReader extends JsonReader {
}
}
/**
* Throws a new IO exception with the given message and a context snippet
* with this reader's content.
*/
private JsonEncodingException syntaxError(String message) throws JsonEncodingException {
throw new JsonEncodingException(message + " at path " + getPath());
}
@Override void promoteNameToValue() throws IOException {
if (hasNext()) {
peekedString = nextName();

View File

@@ -171,9 +171,21 @@ import okio.ByteString;
* of this class are not thread safe.
*/
public abstract class JsonReader implements Closeable {
/**
* Returns a new instance that reads a JSON-encoded stream from {@code source}.
*/
// The nesting stack. Using a manual array rather than an ArrayList saves 20%. This stack permits
// up to 32 levels of nesting including the top-level document. Deeper nesting is prone to trigger
// StackOverflowErrors.
int stackSize = 0;
final int[] scopes = new int[32];
final String[] pathNames = new String[32];
final int[] pathIndices = new int[32];
/** True to accept non-spec compliant JSON */
boolean lenient;
/** True to throw a {@link JsonDataException} on any attempt to call {@link #skipValue()}. */
boolean failOnUnknown;
/** Returns a new instance that reads a JSON-encoded stream from {@code source}. */
public static JsonReader of(BufferedSource source) {
return new BufferedSourceJsonReader(source);
}
@@ -182,6 +194,31 @@ public abstract class JsonReader implements Closeable {
// Package-private to control subclasses.
}
final void pushScope(int newTop) {
if (stackSize == scopes.length) {
throw new JsonDataException("Nesting too deep at " + getPath());
}
scopes[stackSize++] = newTop;
}
/**
* Throws a new IO exception with the given message and a context snippet
* with this reader's content.
*/
final JsonEncodingException syntaxError(String message) throws JsonEncodingException {
throw new JsonEncodingException(message + " at path " + getPath());
}
final JsonDataException typeMismatch(Object value, Object expected) {
if (value == null) {
return new JsonDataException(
"Expected " + expected + " but was null at path " + getPath());
} else {
return new JsonDataException("Expected " + expected + " but was " + value + ", a "
+ value.getClass().getName() + ", at path " + getPath());
}
}
/**
* Configure this parser to be liberal in what it accepts. By default
* this parser is strict and only accepts JSON as specified by <a
@@ -207,12 +244,16 @@ public abstract class JsonReader implements Closeable {
* <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
* </ul>
*/
public abstract void setLenient(boolean lenient);
public final void setLenient(boolean lenient) {
this.lenient = lenient;
}
/**
* Returns true if this parser is liberal in what it accepts.
*/
public abstract boolean isLenient();
public final boolean isLenient() {
return lenient;
}
/**
* Configure whether this parser throws a {@link JsonDataException} when {@link #skipValue} is
@@ -222,12 +263,16 @@ public abstract class JsonReader implements Closeable {
* useful in development and debugging because it means a typo like "locatiom" will be detected
* early. It's potentially harmful in production because it complicates revising a JSON schema.
*/
public abstract void setFailOnUnknown(boolean failOnUnknown);
public final void setFailOnUnknown(boolean failOnUnknown) {
this.failOnUnknown = failOnUnknown;
}
/**
* Returns true if this parser forbids skipping values.
*/
public abstract boolean failOnUnknown();
public final boolean failOnUnknown() {
return failOnUnknown;
}
/**
* Consumes the next token from the JSON stream and asserts that it is the beginning of a new
@@ -349,7 +394,9 @@ public abstract class JsonReader implements Closeable {
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
* the current location in the JSON value.
*/
public abstract String getPath();
public final String getPath() {
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
}
/**
* Changes the reader to treat the next name as a string value. This is useful for map adapters so

View File

@@ -116,9 +116,23 @@ import okio.BufferedSink;
* malformed JSON string will fail with an {@link IllegalStateException}.
*/
public abstract class JsonWriter implements Closeable, Flushable {
// The nesting stack. Using a manual array rather than an ArrayList saves 20%. This stack permits
// up to 32 levels of nesting including the top-level document. Deeper nesting is prone to trigger
// StackOverflowErrors.
int stackSize = 0;
final int[] scopes = new int[32];
final String[] pathNames = new String[32];
final int[] pathIndices = new int[32];
/**
* Returns a new instance that writes a JSON-encoded stream to {@code sink}.
* A string containing a full set of spaces for a single level of indentation, or null for no
* pretty printing.
*/
String indent;
boolean lenient;
boolean serializeNulls;
/** Returns a new instance that writes a JSON-encoded stream to {@code sink}. */
public static JsonWriter of(BufferedSink sink) {
return new BufferedSinkJsonWriter(sink);
}
@@ -127,6 +141,26 @@ public abstract class JsonWriter implements Closeable, Flushable {
// Package-private to control subclasses.
}
/** Returns the scope on the top of the stack. */
final int peekScope() {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
return scopes[stackSize - 1];
}
final void pushScope(int newTop) {
if (stackSize == scopes.length) {
throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?");
}
scopes[stackSize++] = newTop;
}
/** Replace the value on the top of the stack with the given value. */
final void replaceTop(int topOfStack) {
scopes[stackSize - 1] = topOfStack;
}
/**
* Sets the indentation string to be repeated for each level of indentation
* in the encoded document. If {@code indent.isEmpty()} the encoded document
@@ -135,13 +169,17 @@ public abstract class JsonWriter implements Closeable, Flushable {
*
* @param indent a string containing only whitespace.
*/
public abstract void setIndent(String indent);
public void setIndent(String indent) {
this.indent = !indent.isEmpty() ? indent : null;
}
/**
* Returns a string containing only whitespace, used for each level of
* indentation. If empty, the encoded document will be compact.
*/
public abstract String getIndent();
public final String getIndent() {
return indent != null ? indent : "";
}
/**
* Configure this writer to relax its syntax rules. By default, this writer
@@ -155,24 +193,32 @@ public abstract class JsonWriter implements Closeable, Flushable {
* Double#isInfinite() infinities}.
* </ul>
*/
public abstract void setLenient(boolean lenient);
public final void setLenient(boolean lenient) {
this.lenient = lenient;
}
/**
* Returns true if this writer has relaxed syntax rules.
*/
public abstract boolean isLenient();
public final boolean isLenient() {
return lenient;
}
/**
* Sets whether object members are serialized when their value is null.
* This has no impact on array elements. The default is false.
*/
public abstract void setSerializeNulls(boolean serializeNulls);
public final void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls;
}
/**
* Returns true if object members are serialized when their value is null.
* This has no impact on array elements. The default is false.
*/
public abstract boolean getSerializeNulls();
public final boolean getSerializeNulls() {
return serializeNulls;
}
/**
* Begins encoding a new array. Each call to this method must be paired with
@@ -276,5 +322,7 @@ public abstract class JsonWriter implements Closeable, Flushable {
* Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
* the current location in the JSON value.
*/
public abstract String getPath();
public final String getPath() {
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
}
}

View File

@@ -48,35 +48,13 @@ final class ObjectJsonReader extends JsonReader {
/** Sentinel object pushed on {@link #stack} when the reader is closed. */
private static final Object JSON_READER_CLOSED = new Object();
private int stackSize = 0;
private final Object[] stack = new Object[32];
private final int[] scopes = new int[32];
private final String[] pathNames = new String[32];
private final int[] pathIndices = new int[32];
private boolean lenient;
private boolean failOnUnknown;
public ObjectJsonReader(Object root) {
scopes[stackSize] = JsonScope.NONEMPTY_DOCUMENT;
stack[stackSize++] = root;
}
@Override public void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public void setFailOnUnknown(boolean failOnUnknown) {
this.failOnUnknown = failOnUnknown;
}
@Override public boolean failOnUnknown() {
return failOnUnknown;
}
@Override public void beginArray() throws IOException {
List<?> peeked = require(List.class, Token.BEGIN_ARRAY);
@@ -294,10 +272,6 @@ final class ObjectJsonReader extends JsonReader {
}
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
}
@Override void promoteNameToValue() throws IOException {
Map.Entry<?, ?> peeked = require(Map.Entry.class, Token.NAME);
@@ -344,16 +318,6 @@ final class ObjectJsonReader extends JsonReader {
throw typeMismatch(name, Token.NAME);
}
private JsonDataException typeMismatch(Object value, Object expected) {
if (value == null) {
return new JsonDataException(
"Expected " + expected + " but was null at path " + getPath());
} else {
return new JsonDataException("Expected " + expected + " but was " + value + ", a "
+ value.getClass().getName() + ", at path " + getPath());
}
}
/**
* Removes a value and prepares for the next. If we're iterating a map or list this advances the
* iterator.

View File

@@ -27,19 +27,11 @@ import static com.squareup.moshi.JsonScope.NONEMPTY_DOCUMENT;
/** Writes JSON by building a Java object comprising maps, lists, and JSON primitives. */
final class ObjectJsonWriter extends JsonWriter {
private String indent;
private boolean lenient;
private boolean serializeNulls;
private final Object[] stack = new Object[32];
private final int[] scopes = new int[32];
private final String[] pathNames = new String[32];
private final int[] pathIndices = new int[32];
private int stackSize = 0;
private String deferredName;
ObjectJsonWriter() {
scopes[stackSize++] = EMPTY_DOCUMENT;
pushScope(EMPTY_DOCUMENT);
}
public Object root() {
@@ -50,30 +42,6 @@ final class ObjectJsonWriter extends JsonWriter {
return stack[0];
}
@Override public void setIndent(String indent) {
this.indent = indent;
}
@Override public String getIndent() {
return indent;
}
@Override public void setLenient(boolean lenient) {
this.lenient = lenient;
}
@Override public boolean isLenient() {
return lenient;
}
@Override public void setSerializeNulls(boolean serializeNulls) {
this.serializeNulls = serializeNulls;
}
@Override public boolean getSerializeNulls() {
return serializeNulls;
}
@Override public JsonWriter beginArray() throws IOException {
if (stackSize == stack.length) {
throw new JsonDataException("Nesting too deep at " + getPath() + ": circular reference?");
@@ -81,14 +49,13 @@ final class ObjectJsonWriter extends JsonWriter {
List<Object> list = new ArrayList<>();
add(list);
stack[stackSize] = list;
scopes[stackSize] = EMPTY_ARRAY;
pathIndices[stackSize] = 0;
stackSize++;
pushScope(EMPTY_ARRAY);
return this;
}
@Override public JsonWriter endArray() throws IOException {
if (peek() != EMPTY_ARRAY) {
if (peekScope() != EMPTY_ARRAY) {
throw new IllegalStateException("Nesting problem.");
}
stackSize--;
@@ -104,13 +71,12 @@ final class ObjectJsonWriter extends JsonWriter {
Map<String, Object> map = new LinkedHashTreeMap<>();
add(map);
stack[stackSize] = map;
scopes[stackSize] = EMPTY_OBJECT;
stackSize++;
pushScope(EMPTY_OBJECT);
return this;
}
@Override public JsonWriter endObject() throws IOException {
if (peek() != EMPTY_OBJECT || deferredName != null) {
if (peekScope() != EMPTY_OBJECT || deferredName != null) {
throw new IllegalStateException("Nesting problem.");
}
stackSize--;
@@ -127,7 +93,7 @@ final class ObjectJsonWriter extends JsonWriter {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
if (peek() != EMPTY_OBJECT || deferredName != null) {
if (peekScope() != EMPTY_OBJECT || deferredName != null) {
throw new IllegalStateException("Nesting problem.");
}
pathNames[stackSize - 1] = name;
@@ -136,19 +102,27 @@ final class ObjectJsonWriter extends JsonWriter {
}
@Override public JsonWriter value(String value) throws IOException {
return add(value);
add(value);
pathIndices[stackSize - 1]++;
return this;
}
@Override public JsonWriter nullValue() throws IOException {
return add(null);
add(null);
pathIndices[stackSize - 1]++;
return this;
}
@Override public JsonWriter value(boolean value) throws IOException {
return add(value);
add(value);
pathIndices[stackSize - 1]++;
return this;
}
@Override public JsonWriter value(Boolean value) throws IOException {
return add(value);
add(value);
pathIndices[stackSize - 1]++;
return this;
}
@Override public JsonWriter value(double value) throws IOException {
@@ -156,7 +130,9 @@ final class ObjectJsonWriter extends JsonWriter {
}
@Override public JsonWriter value(long value) throws IOException {
return add(value);
add(value);
pathIndices[stackSize - 1]++;
return this;
}
@Override public JsonWriter value(Number value) throws IOException {
@@ -166,17 +142,15 @@ final class ObjectJsonWriter extends JsonWriter {
throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
}
}
return add(value);
add(value);
pathIndices[stackSize - 1]++;
return this;
}
@Override void promoteNameToValue() throws IOException {
throw new UnsupportedOperationException();
}
@Override public String getPath() {
return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices);
}
@Override public void close() throws IOException {
int size = stackSize;
if (size > 1 || size == 1 && scopes[size - 1] != NONEMPTY_DOCUMENT) {
@@ -191,18 +165,8 @@ final class ObjectJsonWriter extends JsonWriter {
}
}
/**
* Returns the scope on the top of the stack.
*/
private int peek() {
if (stackSize == 0) {
throw new IllegalStateException("JsonWriter is closed.");
}
return scopes[stackSize - 1];
}
private ObjectJsonWriter add(Object newTop) {
int scope = peek();
int scope = peekScope();
if (stackSize == 1) {
if (scope != EMPTY_DOCUMENT) {

View File

@@ -17,14 +17,27 @@ package com.squareup.moshi;
import java.io.IOException;
import java.math.BigInteger;
import okio.Buffer;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
@RunWith(Parameterized.class)
public final class JsonWriterPathTest {
@Parameter public JsonWriterFactory factory;
@Parameters(name = "{0}")
public static List<Object[]> parameters() {
return JsonWriterFactory.factories();
}
@Test public void path() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
@@ -63,7 +76,7 @@ public final class JsonWriterPathTest {
}
@Test public void arrayOfObjects() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.beginObject();
@@ -83,7 +96,7 @@ public final class JsonWriterPathTest {
}
@Test public void arrayOfArrays() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
writer.beginArray();
@@ -103,7 +116,7 @@ public final class JsonWriterPathTest {
}
@Test public void objectPath() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
@@ -122,7 +135,7 @@ public final class JsonWriterPathTest {
}
@Test public void nestedObjects() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();
assertThat(writer.getPath()).isEqualTo("$.");
@@ -147,7 +160,7 @@ public final class JsonWriterPathTest {
}
@Test public void arrayPath() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
@@ -168,7 +181,7 @@ public final class JsonWriterPathTest {
}
@Test public void nestedArrays() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
assertThat(writer.getPath()).isEqualTo("$");
writer.beginArray();
assertThat(writer.getPath()).isEqualTo("$[0]");
@@ -189,7 +202,9 @@ public final class JsonWriterPathTest {
}
@Test public void multipleTopLevelValuesInOneDocument() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
assumeTrue(factory.supportsMultipleTopLevelValuesInOneDocument());
JsonWriter writer = factory.newWriter();
writer.setLenient(true);
writer.beginArray();
writer.endArray();
@@ -200,7 +215,7 @@ public final class JsonWriterPathTest {
}
@Test public void skipNulls() throws IOException {
JsonWriter writer = JsonWriter.of(new Buffer());
JsonWriter writer = factory.newWriter();
writer.setSerializeNulls(false);
assertThat(writer.getPath()).isEqualTo("$");
writer.beginObject();