diff --git a/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java b/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java index 6fa32f3..1260b81 100644 --- a/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/BufferedSinkJsonWriter.java @@ -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); - } } diff --git a/moshi/src/main/java/com/squareup/moshi/BufferedSourceJsonReader.java b/moshi/src/main/java/com/squareup/moshi/BufferedSourceJsonReader.java index c3f5f3e..de4bccb 100644 --- a/moshi/src/main/java/com/squareup/moshi/BufferedSourceJsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/BufferedSourceJsonReader.java @@ -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(); diff --git a/moshi/src/main/java/com/squareup/moshi/JsonReader.java b/moshi/src/main/java/com/squareup/moshi/JsonReader.java index b3ee721..0ff01a7 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonReader.java @@ -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 Name/value pairs separated by {@code ;} instead of {@code ,}. * */ - 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 JsonPath 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 diff --git a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java index f8d1e4e..e178760 100644 --- a/moshi/src/main/java/com/squareup/moshi/JsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/JsonWriter.java @@ -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}. * */ - 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 JsonPath to * the current location in the JSON value. */ - public abstract String getPath(); + public final String getPath() { + return JsonScope.getPath(stackSize, scopes, pathNames, pathIndices); + } } diff --git a/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java b/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java index 37d628d..8f16fec 100644 --- a/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java +++ b/moshi/src/main/java/com/squareup/moshi/ObjectJsonReader.java @@ -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. diff --git a/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java b/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java index 15a1174..a4b7c53 100644 --- a/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java +++ b/moshi/src/main/java/com/squareup/moshi/ObjectJsonWriter.java @@ -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 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 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) { diff --git a/moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java b/moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java index 3f342b5..bce20c5 100644 --- a/moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java +++ b/moshi/src/test/java/com/squareup/moshi/JsonWriterPathTest.java @@ -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 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();