Merge pull request #2 from square/jwilson_0809_momoshi

Make JsonReader and JsonWriter our own.
This commit is contained in:
Jesse Wilson
2014-08-10 00:13:04 -04:00
5 changed files with 575 additions and 807 deletions

View File

@@ -18,7 +18,9 @@ package com.squareup.moshi;
import java.io.Closeable; import java.io.Closeable;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import okio.Buffer;
import okio.BufferedSource;
import okio.Source;
/** /**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>) * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
@@ -166,26 +168,12 @@ import java.io.Reader;
* precision loss, extremely large values should be written and read as strings * precision loss, extremely large values should be written and read as strings
* in JSON. * in JSON.
* *
* <a name="nonexecuteprefix"/><h3>Non-Execute Prefix</h3>
* Web servers that serve private data using JSON may be vulnerable to <a
* href="http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site
* request forgery</a> attacks. In such an attack, a malicious site gains access
* to a private JSON file by executing it with an HTML {@code <script>} tag.
*
* <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable
* by {@code <script>} tags, disarming the attack. Since the prefix is malformed
* JSON, strict parsing fails when it is encountered. This class permits the
* non-execute prefix when {@link #setLenient(boolean) lenient parsing} is
* enabled.
*
* <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
* of this class are not thread safe. * of this class are not thread safe.
* *
* @author Jesse Wilson * @author Jesse Wilson
*/ */
public class JsonReader implements Closeable { public class JsonReader implements Closeable {
/** The only non-execute prefix this parser permits */
private static final char[] NON_EXECUTE_PREFIX = ")]}'\n".toCharArray();
private static final long MIN_INCOMPLETE_INTEGER = Long.MIN_VALUE / 10; private static final long MIN_INCOMPLETE_INTEGER = Long.MIN_VALUE / 10;
private static final int PEEKED_NONE = 0; private static final int PEEKED_NONE = 0;
@@ -219,24 +207,13 @@ public class JsonReader implements Closeable {
private static final int NUMBER_CHAR_EXP_SIGN = 6; private static final int NUMBER_CHAR_EXP_SIGN = 6;
private static final int NUMBER_CHAR_EXP_DIGIT = 7; private static final int NUMBER_CHAR_EXP_DIGIT = 7;
/** The input JSON. */
private final Reader in;
/** True to accept non-spec compliant JSON */ /** True to accept non-spec compliant JSON */
private boolean lenient = false; private boolean lenient = false;
/** /** The input JSON. */
* Use a manual buffer to easily read and unread upcoming characters, and private final BufferedSource source;
* also so we can create strings without an intermediate StringBuilder. private final Buffer buffer;
* We decode literals directly out of this buffer, so it must be at least as
* long as the longest token that can be reported as a number.
*/
private final char[] buffer = new char[1024];
private int pos = 0;
private int limit = 0;
private int lineNumber = 0;
private int lineStart = 0;
private int peeked = PEEKED_NONE; private int peeked = PEEKED_NONE;
@@ -282,11 +259,19 @@ public class JsonReader implements Closeable {
/** /**
* Creates a new instance that reads a JSON-encoded stream from {@code in}. * Creates a new instance that reads a JSON-encoded stream from {@code in}.
*/ */
public JsonReader(Reader in) { public JsonReader(Source source) {
if (in == null) { if (source == null) {
throw new NullPointerException("in == null"); throw new NullPointerException("source == null");
} }
this.in = in; throw new UnsupportedOperationException("TODO");
}
/**
* Creates a new instance that reads a JSON-encoded value {@code s}.
*/
public JsonReader(String s) {
this.source = new Buffer().writeUtf8(s);
this.buffer = new Buffer();
} }
/** /**
@@ -344,7 +329,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} else { } else {
throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek() throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
} }
@@ -362,7 +347,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} else { } else {
throw new IllegalStateException("Expected END_ARRAY but was " + peek() throw new IllegalStateException("Expected END_ARRAY but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
} }
@@ -380,7 +365,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} else { } else {
throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek() throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
} }
@@ -399,7 +384,7 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} else { } else {
throw new IllegalStateException("Expected END_OBJECT but was " + peek() throw new IllegalStateException("Expected END_OBJECT but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
} }
@@ -463,6 +448,7 @@ public class JsonReader implements Closeable {
} else if (peekStack == JsonScope.NONEMPTY_ARRAY) { } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
// Look for a comma before the next element. // Look for a comma before the next element.
int c = nextNonWhitespace(true); int c = nextNonWhitespace(true);
buffer.readByte(); // consume ']' or ','.
switch (c) { switch (c) {
case ']': case ']':
return peeked = PEEKED_END_ARRAY; return peeked = PEEKED_END_ARRAY;
@@ -478,6 +464,7 @@ public class JsonReader implements Closeable {
// Look for a comma before the next element. // Look for a comma before the next element.
if (peekStack == JsonScope.NONEMPTY_OBJECT) { if (peekStack == JsonScope.NONEMPTY_OBJECT) {
int c = nextNonWhitespace(true); int c = nextNonWhitespace(true);
buffer.readByte(); // Consume '}' or ','.
switch (c) { switch (c) {
case '}': case '}':
return peeked = PEEKED_END_OBJECT; return peeked = PEEKED_END_OBJECT;
@@ -492,19 +479,21 @@ public class JsonReader implements Closeable {
int c = nextNonWhitespace(true); int c = nextNonWhitespace(true);
switch (c) { switch (c) {
case '"': case '"':
buffer.readByte(); // consume the '\"'.
return peeked = PEEKED_DOUBLE_QUOTED_NAME; return peeked = PEEKED_DOUBLE_QUOTED_NAME;
case '\'': case '\'':
buffer.readByte(); // consume the '\''.
checkLenient(); checkLenient();
return peeked = PEEKED_SINGLE_QUOTED_NAME; return peeked = PEEKED_SINGLE_QUOTED_NAME;
case '}': case '}':
if (peekStack != JsonScope.NONEMPTY_OBJECT) { if (peekStack != JsonScope.NONEMPTY_OBJECT) {
buffer.readByte(); // consume the '}'.
return peeked = PEEKED_END_OBJECT; return peeked = PEEKED_END_OBJECT;
} else { } else {
throw syntaxError("Expected name"); throw syntaxError("Expected name");
} }
default: default:
checkLenient(); checkLenient();
pos--; // Don't consume the first character in an unquoted string.
if (isLiteral((char) c)) { if (isLiteral((char) c)) {
return peeked = PEEKED_UNQUOTED_NAME; return peeked = PEEKED_UNQUOTED_NAME;
} else { } else {
@@ -515,22 +504,20 @@ public class JsonReader implements Closeable {
stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT; stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
// Look for a colon before the value. // Look for a colon before the value.
int c = nextNonWhitespace(true); int c = nextNonWhitespace(true);
buffer.readByte(); // Consume ':'.
switch (c) { switch (c) {
case ':': case ':':
break; break;
case '=': case '=':
checkLenient(); checkLenient();
if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') { if (fillBuffer(1) && buffer.getByte(0) == '>') {
pos++; buffer.readByte(); // Consume '>'.
} }
break; break;
default: default:
throw syntaxError("Expected ':'"); throw syntaxError("Expected ':'");
} }
} else if (peekStack == JsonScope.EMPTY_DOCUMENT) { } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
if (lenient) {
consumeNonExecutePrefix();
}
stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT; stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
} else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) { } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
int c = nextNonWhitespace(false); int c = nextNonWhitespace(false);
@@ -538,7 +525,6 @@ public class JsonReader implements Closeable {
return peeked = PEEKED_EOF; return peeked = PEEKED_EOF;
} else { } else {
checkLenient(); checkLenient();
pos--;
} }
} else if (peekStack == JsonScope.CLOSED) { } else if (peekStack == JsonScope.CLOSED) {
throw new IllegalStateException("JsonReader is closed"); throw new IllegalStateException("JsonReader is closed");
@@ -548,6 +534,7 @@ public class JsonReader implements Closeable {
switch (c) { switch (c) {
case ']': case ']':
if (peekStack == JsonScope.EMPTY_ARRAY) { if (peekStack == JsonScope.EMPTY_ARRAY) {
buffer.readByte(); // Consume ']'.
return peeked = PEEKED_END_ARRAY; return peeked = PEEKED_END_ARRAY;
} }
// fall-through to handle ",]" // fall-through to handle ",]"
@@ -556,25 +543,27 @@ public class JsonReader implements Closeable {
// In lenient mode, a 0-length literal in an array means 'null'. // In lenient mode, a 0-length literal in an array means 'null'.
if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) { if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
checkLenient(); checkLenient();
pos--;
return peeked = PEEKED_NULL; return peeked = PEEKED_NULL;
} else { } else {
throw syntaxError("Unexpected value"); throw syntaxError("Unexpected value");
} }
case '\'': case '\'':
checkLenient(); checkLenient();
buffer.readByte(); // Consume '\''.
return peeked = PEEKED_SINGLE_QUOTED; return peeked = PEEKED_SINGLE_QUOTED;
case '"': case '"':
if (stackSize == 1) { if (stackSize == 1) {
checkLenient(); checkLenient();
} }
buffer.readByte(); // Consume '\"'.
return peeked = PEEKED_DOUBLE_QUOTED; return peeked = PEEKED_DOUBLE_QUOTED;
case '[': case '[':
buffer.readByte(); // Consume '['.
return peeked = PEEKED_BEGIN_ARRAY; return peeked = PEEKED_BEGIN_ARRAY;
case '{': case '{':
buffer.readByte(); // Consume ']'.
return peeked = PEEKED_BEGIN_OBJECT; return peeked = PEEKED_BEGIN_OBJECT;
default: default:
pos--; // Don't consume the first character in a literal value.
} }
if (stackSize == 1) { if (stackSize == 1) {
@@ -591,7 +580,7 @@ public class JsonReader implements Closeable {
return result; return result;
} }
if (!isLiteral(buffer[pos])) { if (!isLiteral(buffer.getByte(0))) {
throw syntaxError("Expected value"); throw syntaxError("Expected value");
} }
@@ -601,7 +590,7 @@ public class JsonReader implements Closeable {
private int peekKeyword() throws IOException { private int peekKeyword() throws IOException {
// Figure out which keyword we're matching against by its first character. // Figure out which keyword we're matching against by its first character.
char c = buffer[pos]; byte c = buffer.getByte(0);
String keyword; String keyword;
String keywordUpper; String keywordUpper;
int peeking; int peeking;
@@ -624,31 +613,25 @@ public class JsonReader implements Closeable {
// Confirm that chars [1..length) match the keyword. // Confirm that chars [1..length) match the keyword.
int length = keyword.length(); int length = keyword.length();
for (int i = 1; i < length; i++) { for (int i = 1; i < length; i++) {
if (pos + i >= limit && !fillBuffer(i + 1)) { if (!fillBuffer(i + 1)) {
return PEEKED_NONE; return PEEKED_NONE;
} }
c = buffer[pos + i]; c = buffer.getByte(i);
if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) { if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) {
return PEEKED_NONE; return PEEKED_NONE;
} }
} }
if ((pos + length < limit || fillBuffer(length + 1)) if (fillBuffer(length + 1) && isLiteral(buffer.getByte(length))) {
&& isLiteral(buffer[pos + length])) {
return PEEKED_NONE; // Don't match trues, falsey or nullsoft! return PEEKED_NONE; // Don't match trues, falsey or nullsoft!
} }
// We've found the keyword followed either by EOF or by a non-literal character. // We've found the keyword followed either by EOF or by a non-literal character.
pos += length; buffer.skip(length);
return peeked = peeking; return peeked = peeking;
} }
private int peekNumber() throws IOException { private int peekNumber() throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer;
int p = pos;
int l = limit;
long value = 0; // Negative to accommodate Long.MIN_VALUE more easily. long value = 0; // Negative to accommodate Long.MIN_VALUE more easily.
boolean negative = false; boolean negative = false;
boolean fitsInLong = true; boolean fitsInLong = true;
@@ -658,20 +641,11 @@ public class JsonReader implements Closeable {
charactersOfNumber: charactersOfNumber:
for (; true; i++) { for (; true; i++) {
if (p + i == l) {
if (i == buffer.length) {
// Though this looks like a well-formed number, it's too long to continue reading. Give up
// and let the application handle this as an unquoted literal.
return PEEKED_NONE;
}
if (!fillBuffer(i + 1)) { if (!fillBuffer(i + 1)) {
break; break;
} }
p = pos;
l = limit;
}
char c = buffer[p + i]; byte c = buffer.getByte(i);
switch (c) { switch (c) {
case '-': case '-':
if (last == NUMBER_CHAR_NONE) { if (last == NUMBER_CHAR_NONE) {
@@ -735,7 +709,7 @@ public class JsonReader implements Closeable {
// We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER. // We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER.
if (last == NUMBER_CHAR_DIGIT && fitsInLong && (value != Long.MIN_VALUE || negative)) { if (last == NUMBER_CHAR_DIGIT && fitsInLong && (value != Long.MIN_VALUE || negative)) {
peekedLong = negative ? value : -value; peekedLong = negative ? value : -value;
pos += i; buffer.skip(i);
return peeked = PEEKED_LONG; return peeked = PEEKED_LONG;
} else if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT } else if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT
|| last == NUMBER_CHAR_EXP_DIGIT) { || last == NUMBER_CHAR_EXP_DIGIT) {
@@ -746,7 +720,7 @@ public class JsonReader implements Closeable {
} }
} }
private boolean isLiteral(char c) throws IOException { private boolean isLiteral(int c) throws IOException {
switch (c) { switch (c) {
case '/': case '/':
case '\\': case '\\':
@@ -792,7 +766,7 @@ public class JsonReader implements Closeable {
result = nextQuotedValue('"'); result = nextQuotedValue('"');
} else { } else {
throw new IllegalStateException("Expected a name but was " + peek() throw new IllegalStateException("Expected a name but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
pathNames[stackSize - 1] = result; pathNames[stackSize - 1] = result;
@@ -825,11 +799,10 @@ public class JsonReader implements Closeable {
} else if (p == PEEKED_LONG) { } else if (p == PEEKED_LONG) {
result = Long.toString(peekedLong); result = Long.toString(peekedLong);
} else if (p == PEEKED_NUMBER) { } else if (p == PEEKED_NUMBER) {
result = new String(buffer, pos, peekedNumberLength); result = buffer.readUtf8(peekedNumberLength);
pos += peekedNumberLength;
} else { } else {
throw new IllegalStateException("Expected a string but was " + peek() throw new IllegalStateException("Expected a string but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++; pathIndices[stackSize - 1]++;
@@ -858,7 +831,7 @@ public class JsonReader implements Closeable {
return false; return false;
} }
throw new IllegalStateException("Expected a boolean but was " + peek() throw new IllegalStateException("Expected a boolean but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
/** /**
@@ -878,7 +851,7 @@ public class JsonReader implements Closeable {
pathIndices[stackSize - 1]++; pathIndices[stackSize - 1]++;
} else { } else {
throw new IllegalStateException("Expected null but was " + peek() throw new IllegalStateException("Expected null but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
} }
@@ -904,22 +877,21 @@ public class JsonReader implements Closeable {
} }
if (p == PEEKED_NUMBER) { if (p == PEEKED_NUMBER) {
peekedString = new String(buffer, pos, peekedNumberLength); peekedString = buffer.readUtf8(peekedNumberLength);
pos += peekedNumberLength;
} else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
} else if (p == PEEKED_UNQUOTED) { } else if (p == PEEKED_UNQUOTED) {
peekedString = nextUnquotedValue(); peekedString = nextUnquotedValue();
} else if (p != PEEKED_BUFFERED) { } else if (p != PEEKED_BUFFERED) {
throw new IllegalStateException("Expected a double but was " + peek() throw new IllegalStateException("Expected a double but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_BUFFERED; peeked = PEEKED_BUFFERED;
double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException. double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) { if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
throw new IOException("JSON forbids NaN and infinities: " + result throw new IOException("JSON forbids NaN and infinities: " + result
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peekedString = null; peekedString = null;
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
@@ -950,8 +922,7 @@ public class JsonReader implements Closeable {
} }
if (p == PEEKED_NUMBER) { if (p == PEEKED_NUMBER) {
peekedString = new String(buffer, pos, peekedNumberLength); peekedString = buffer.readUtf8(peekedNumberLength);
pos += peekedNumberLength;
} else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
try { try {
@@ -964,7 +935,7 @@ public class JsonReader implements Closeable {
} }
} else { } else {
throw new IllegalStateException("Expected a long but was " + peek() throw new IllegalStateException("Expected a long but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_BUFFERED; peeked = PEEKED_BUFFERED;
@@ -972,7 +943,7 @@ public class JsonReader implements Closeable {
long result = (long) asDouble; long result = (long) asDouble;
if (result != asDouble) { // Make sure no precision was lost casting to 'long'. if (result != asDouble) { // Make sure no precision was lost casting to 'long'.
throw new NumberFormatException("Expected a long but was " + peekedString throw new NumberFormatException("Expected a long but was " + peekedString
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peekedString = null; peekedString = null;
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
@@ -991,54 +962,36 @@ public class JsonReader implements Closeable {
* malformed. * malformed.
*/ */
private String nextQuotedValue(char quote) throws IOException { private String nextQuotedValue(char quote) throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
char[] buffer = this.buffer;
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
while (true) { int p = 0;
int p = pos; while (fillBuffer(p + 1)) {
int l = limit; int c = buffer.getByte(p++);
/* the index of the first character not yet appended to the builder. */
int start = p;
while (p < l) {
int c = buffer[p++];
if (c == quote) { if (c == quote) {
pos = p; builder.append(buffer.readUtf8(p - 1));
builder.append(buffer, start, p - start - 1); buffer.readByte();
return builder.toString(); return builder.toString();
} else if (c == '\\') { } else if (c == '\\') {
pos = p; builder.append(buffer.readUtf8(p - 1));
builder.append(buffer, start, p - start - 1); buffer.readByte(); // '\'
builder.append(readEscapeCharacter()); builder.append(readEscapeCharacter());
p = pos; p = 0;
l = limit;
start = p;
} else if (c == '\n') {
lineNumber++;
lineStart = p;
} }
} }
builder.append(buffer, start, p - start);
pos = p;
if (!fillBuffer(1)) {
throw syntaxError("Unterminated string"); throw syntaxError("Unterminated string");
} }
}
}
/** /**
* Returns an unquoted value as a string. * Returns an unquoted value as a string.
*/ */
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private String nextUnquotedValue() throws IOException { private String nextUnquotedValue() throws IOException {
StringBuilder builder = null;
int i = 0; int i = 0;
findNonLiteralCharacter: findNonStringChar:
while (true) { for (; fillBuffer(i + 1); i++) {
for (; pos + i < limit; i++) { switch (buffer.getByte(i)) {
switch (buffer[pos + i]) {
case '/': case '/':
case '\\': case '\\':
case ';': case ';':
@@ -1056,74 +1009,35 @@ public class JsonReader implements Closeable {
case '\f': case '\f':
case '\r': case '\r':
case '\n': case '\n':
break findNonLiteralCharacter; break findNonStringChar;
} }
} }
// Attempt to load the entire literal into the buffer at once. return buffer.readUtf8(i);
if (i < buffer.length) {
if (fillBuffer(i + 1)) {
continue;
} else {
break;
}
}
// use a StringBuilder when the value is too long. This is too long to be a number!
if (builder == null) {
builder = new StringBuilder();
}
builder.append(buffer, pos, i);
pos += i;
i = 0;
if (!fillBuffer(1)) {
break;
}
}
String result;
if (builder == null) {
result = new String(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
result = builder.toString();
}
pos += i;
return result;
} }
private void skipQuotedValue(char quote) throws IOException { private void skipQuotedValue(char quote) throws IOException {
// Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access. int p = 0;
char[] buffer = this.buffer; while (fillBuffer(p + 1)) {
do { int c = buffer.getByte(p++);
int p = pos;
int l = limit;
/* the index of the first character not yet appended to the builder. */
while (p < l) {
int c = buffer[p++];
if (c == quote) { if (c == quote) {
pos = p; buffer.skip(p);
return; return;
} else if (c == '\\') { } else if (c == '\\') {
pos = p; buffer.skip(p);
readEscapeCharacter(); readEscapeCharacter();
p = pos; p = 0;
l = limit;
} else if (c == '\n') {
lineNumber++;
lineStart = p;
} }
} }
pos = p;
} while (fillBuffer(1));
throw syntaxError("Unterminated string"); throw syntaxError("Unterminated string");
} }
private void skipUnquotedValue() throws IOException { private void skipUnquotedValue() throws IOException {
do {
int i = 0; int i = 0;
for (; pos + i < limit; i++) {
switch (buffer[pos + i]) { findNonStringChar:
for (; fillBuffer(i + 1); i++) {
switch (buffer.getByte(i)) {
case '/': case '/':
case '\\': case '\\':
case ';': case ';':
@@ -1141,12 +1055,11 @@ public class JsonReader implements Closeable {
case '\f': case '\f':
case '\r': case '\r':
case '\n': case '\n':
pos += i; break findNonStringChar;
return;
} }
} }
pos += i;
} while (fillBuffer(1)); buffer.skip(i);
} }
/** /**
@@ -1170,7 +1083,7 @@ public class JsonReader implements Closeable {
result = (int) peekedLong; result = (int) peekedLong;
if (peekedLong != result) { // Make sure no precision was lost casting to 'int'. if (peekedLong != result) { // Make sure no precision was lost casting to 'int'.
throw new NumberFormatException("Expected an int but was " + peekedLong throw new NumberFormatException("Expected an int but was " + peekedLong
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
pathIndices[stackSize - 1]++; pathIndices[stackSize - 1]++;
@@ -1178,8 +1091,7 @@ public class JsonReader implements Closeable {
} }
if (p == PEEKED_NUMBER) { if (p == PEEKED_NUMBER) {
peekedString = new String(buffer, pos, peekedNumberLength); peekedString = buffer.readUtf8(peekedNumberLength);
pos += peekedNumberLength;
} else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) { } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"'); peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
try { try {
@@ -1192,7 +1104,7 @@ public class JsonReader implements Closeable {
} }
} else { } else {
throw new IllegalStateException("Expected an int but was " + peek() throw new IllegalStateException("Expected an int but was " + peek()
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peeked = PEEKED_BUFFERED; peeked = PEEKED_BUFFERED;
@@ -1200,7 +1112,7 @@ public class JsonReader implements Closeable {
result = (int) asDouble; result = (int) asDouble;
if (result != asDouble) { // Make sure no precision was lost casting to 'int'. if (result != asDouble) { // Make sure no precision was lost casting to 'int'.
throw new NumberFormatException("Expected an int but was " + peekedString throw new NumberFormatException("Expected an int but was " + peekedString
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath()); + " at path " + getPath());
} }
peekedString = null; peekedString = null;
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
@@ -1215,7 +1127,8 @@ public class JsonReader implements Closeable {
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
stack[0] = JsonScope.CLOSED; stack[0] = JsonScope.CLOSED;
stackSize = 1; stackSize = 1;
in.close(); buffer.clear();
source.close();
} }
/** /**
@@ -1250,7 +1163,7 @@ public class JsonReader implements Closeable {
} else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) { } else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) {
skipQuotedValue('"'); skipQuotedValue('"');
} else if (p == PEEKED_NUMBER) { } else if (p == PEEKED_NUMBER) {
pos += peekedNumberLength; buffer.skip(peekedNumberLength);
} }
peeked = PEEKED_NONE; peeked = PEEKED_NONE;
} while (count != 0); } while (count != 0);
@@ -1280,41 +1193,11 @@ public class JsonReader implements Closeable {
* false. * false.
*/ */
private boolean fillBuffer(int minimum) throws IOException { private boolean fillBuffer(int minimum) throws IOException {
char[] buffer = this.buffer; while (buffer.size() < minimum) {
lineStart -= pos; if (source.read(buffer, 2048) == -1) return false;
if (limit != pos) {
limit -= pos;
System.arraycopy(buffer, pos, buffer, 0, limit);
} else {
limit = 0;
} }
pos = 0;
int total;
while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
limit += total;
// if this is the first read, consume an optional byte order mark (BOM) if it exists
if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\ufeff') {
pos++;
lineStart++;
minimum++;
}
if (limit >= minimum) {
return true; return true;
} }
}
return false;
}
private int getLineNumber() {
return lineNumber + 1;
}
private int getColumnNumber() {
return pos - lineStart + 1;
}
/** /**
* Returns the next character in the stream that is neither whitespace nor a * Returns the next character in the stream that is neither whitespace nor a
@@ -1331,65 +1214,46 @@ public class JsonReader implements Closeable {
* before any (potentially indirect) call to fillBuffer() and reread both * before any (potentially indirect) call to fillBuffer() and reread both
* 'p' and 'l' after any (potentially indirect) call to the same method. * 'p' and 'l' after any (potentially indirect) call to the same method.
*/ */
char[] buffer = this.buffer; int p = 0;
int p = pos; while (fillBuffer(p + 1)) {
int l = limit; int c = buffer.getByte(p++);
while (true) { if (c == '\n' || c == ' ' || c == '\r' || c == '\t') {
if (p == l) {
pos = p;
if (!fillBuffer(1)) {
break;
}
p = pos;
l = limit;
}
int c = buffer[p++];
if (c == '\n') {
lineNumber++;
lineStart = p;
continue;
} else if (c == ' ' || c == '\r' || c == '\t') {
continue; continue;
} }
buffer.skip(p - 1);
if (c == '/') { if (c == '/') {
pos = p; if (!fillBuffer(2)) {
if (p == l) {
pos--; // push back '/' so it's still in the buffer when this method returns
boolean charsLoaded = fillBuffer(2);
pos++; // consume the '/' again
if (!charsLoaded) {
return c; return c;
} }
}
checkLenient(); checkLenient();
char peek = buffer[pos]; byte peek = buffer.getByte(1);
switch (peek) { switch (peek) {
case '*': case '*':
// skip a /* c-style comment */ // skip a /* c-style comment */
pos++; buffer.readByte(); // '/'
buffer.readByte(); // '*'
if (!skipTo("*/")) { if (!skipTo("*/")) {
throw syntaxError("Unterminated comment"); throw syntaxError("Unterminated comment");
} }
p = pos + 2; buffer.readByte(); // '*'
l = limit; buffer.readByte(); // '/'
p = 0;
continue; continue;
case '/': case '/':
// skip a // end-of-line comment // skip a // end-of-line comment
pos++; buffer.readByte(); // '/'
buffer.readByte(); // '/'
skipToEndOfLine(); skipToEndOfLine();
p = pos; p = 0;
l = limit;
continue; continue;
default: default:
return c; return c;
} }
} else if (c == '#') { } else if (c == '#') {
pos = p;
/* /*
* Skip a # hash end-of-line comment. The JSON RFC doesn't * Skip a # hash end-of-line comment. The JSON RFC doesn't
* specify this behaviour, but it's required to parse * specify this behaviour, but it's required to parse
@@ -1397,16 +1261,13 @@ public class JsonReader implements Closeable {
*/ */
checkLenient(); checkLenient();
skipToEndOfLine(); skipToEndOfLine();
p = pos; p = 0;
l = limit;
} else { } else {
pos = p;
return c; return c;
} }
} }
if (throwOnEof) { if (throwOnEof) {
throw new EOFException("End of input" throw new EOFException("End of input");
+ " at line " + getLineNumber() + " column " + getColumnNumber());
} else { } else {
return -1; return -1;
} }
@@ -1424,13 +1285,9 @@ public class JsonReader implements Closeable {
* caller. * caller.
*/ */
private void skipToEndOfLine() throws IOException { private void skipToEndOfLine() throws IOException {
while (pos < limit || fillBuffer(1)) { while (fillBuffer(1)) {
char c = buffer[pos++]; byte c = buffer.readByte();
if (c == '\n') { if (c == '\n' || c == '\r') {
lineNumber++;
lineStart = pos;
break;
} else if (c == '\r') {
break; break;
} }
} }
@@ -1441,14 +1298,10 @@ public class JsonReader implements Closeable {
*/ */
private boolean skipTo(String toFind) throws IOException { private boolean skipTo(String toFind) throws IOException {
outer: outer:
for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) { for (; fillBuffer(toFind.length());) {
if (buffer[pos] == '\n') {
lineNumber++;
lineStart = pos + 1;
continue;
}
for (int c = 0; c < toFind.length(); c++) { for (int c = 0; c < toFind.length(); c++) {
if (buffer[pos + c] != toFind.charAt(c)) { if (buffer.getByte(c) != toFind.charAt(c)) {
buffer.readByte();
continue outer; continue outer;
} }
} }
@@ -1458,8 +1311,7 @@ public class JsonReader implements Closeable {
} }
@Override public String toString() { @Override public String toString() {
return getClass().getSimpleName() return getClass().getSimpleName();
+ " at line " + getLineNumber() + " column " + getColumnNumber();
} }
/** /**
@@ -1503,20 +1355,20 @@ public class JsonReader implements Closeable {
* malformed. * malformed.
*/ */
private char readEscapeCharacter() throws IOException { private char readEscapeCharacter() throws IOException {
if (pos == limit && !fillBuffer(1)) { if (!fillBuffer(1)) {
throw syntaxError("Unterminated escape sequence"); throw syntaxError("Unterminated escape sequence");
} }
char escaped = buffer[pos++]; byte escaped = buffer.readByte();
switch (escaped) { switch (escaped) {
case 'u': case 'u':
if (pos + 4 > limit && !fillBuffer(4)) { if (!fillBuffer(4)) {
throw syntaxError("Unterminated escape sequence"); throw syntaxError("Unterminated escape sequence");
} }
// Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16); // Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16);
char result = 0; char result = 0;
for (int i = pos, end = i + 4; i < end; i++) { for (int i = 0, end = i + 4; i < end; i++) {
char c = buffer[i]; byte c = buffer.getByte(i);
result <<= 4; result <<= 4;
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
result += (c - '0'); result += (c - '0');
@@ -1525,10 +1377,10 @@ public class JsonReader implements Closeable {
} else if (c >= 'A' && c <= 'F') { } else if (c >= 'A' && c <= 'F') {
result += (c - 'A' + 10); result += (c - 'A' + 10);
} else { } else {
throw new NumberFormatException("\\u" + new String(buffer, pos, 4)); throw new NumberFormatException("\\u" + buffer.readUtf8(4));
} }
} }
pos += 4; buffer.skip(4);
return result; return result;
case 't': case 't':
@@ -1547,15 +1399,11 @@ public class JsonReader implements Closeable {
return '\f'; return '\f';
case '\n': case '\n':
lineNumber++;
lineStart = pos;
// fall-through
case '\'': case '\'':
case '"': case '"':
case '\\': case '\\':
default: default:
return escaped; return (char) escaped;
} }
} }
@@ -1564,29 +1412,6 @@ public class JsonReader implements Closeable {
* with this reader's content. * with this reader's content.
*/ */
private IOException syntaxError(String message) throws IOException { private IOException syntaxError(String message) throws IOException {
throw new IOException(message throw new IOException(message + " at path " + getPath());
+ " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
}
/**
* Consumes the non-execute prefix if it exists.
*/
private void consumeNonExecutePrefix() throws IOException {
// fast forward through the leading whitespace
nextNonWhitespace(true);
pos--;
if (pos + NON_EXECUTE_PREFIX.length > limit && !fillBuffer(NON_EXECUTE_PREFIX.length)) {
return;
}
for (int i = 0; i < NON_EXECUTE_PREFIX.length; i++) {
if (buffer[pos + i] != NON_EXECUTE_PREFIX[i]) {
return; // not a security token!
}
}
// we consumed a security token!
pos += NON_EXECUTE_PREFIX.length;
} }
} }

View File

@@ -138,7 +138,6 @@ public class JsonWriter implements Closeable, Flushable {
* error. http://code.google.com/p/google-gson/issues/detail?id=341 * error. http://code.google.com/p/google-gson/issues/detail?id=341
*/ */
private static final String[] REPLACEMENT_CHARS; private static final String[] REPLACEMENT_CHARS;
private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
static { static {
REPLACEMENT_CHARS = new String[128]; REPLACEMENT_CHARS = new String[128];
for (int i = 0; i <= 0x1f; i++) { for (int i = 0; i <= 0x1f; i++) {
@@ -151,12 +150,6 @@ public class JsonWriter implements Closeable, Flushable {
REPLACEMENT_CHARS['\n'] = "\\n"; REPLACEMENT_CHARS['\n'] = "\\n";
REPLACEMENT_CHARS['\r'] = "\\r"; REPLACEMENT_CHARS['\r'] = "\\r";
REPLACEMENT_CHARS['\f'] = "\\f"; REPLACEMENT_CHARS['\f'] = "\\f";
HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();
HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";
HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";
HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";
HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";
HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
} }
/** The output data, containing at most one top-level array or object. */ /** The output data, containing at most one top-level array or object. */
@@ -181,11 +174,9 @@ public class JsonWriter implements Closeable, Flushable {
private boolean lenient; private boolean lenient;
private boolean htmlSafe;
private String deferredName; private String deferredName;
private boolean serializeNulls = true; private boolean serializeNulls;
/** /**
* Creates a new instance that writes a JSON-encoded stream to {@code out}. * Creates a new instance that writes a JSON-encoded stream to {@code out}.
@@ -240,25 +231,6 @@ public class JsonWriter implements Closeable, Flushable {
return lenient; return lenient;
} }
/**
* Configure this writer to emit JSON that's safe for direct inclusion in HTML
* and XML documents. This escapes the HTML characters {@code <}, {@code >},
* {@code &} and {@code =} before writing them to the stream. Without this
* setting, your XML/HTML encoder should replace these characters with the
* corresponding escape sequences.
*/
public final void setHtmlSafe(boolean htmlSafe) {
this.htmlSafe = htmlSafe;
}
/**
* Returns true if this writer writes JSON that's safe for inclusion in HTML
* and XML documents.
*/
public final boolean isHtmlSafe() {
return htmlSafe;
}
/** /**
* Sets whether object members are serialized when their value is null. * Sets whether object members are serialized when their value is null.
* This has no impact on array elements. The default is true. * This has no impact on array elements. The default is true.
@@ -528,7 +500,7 @@ public class JsonWriter implements Closeable, Flushable {
} }
private void string(String value) throws IOException { private void string(String value) throws IOException {
String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS; String[] replacements = REPLACEMENT_CHARS;
out.write("\""); out.write("\"");
int last = 0; int last = 0;
int length = value.length(); int length = value.length();

View File

@@ -16,13 +16,13 @@
package com.squareup.moshi; package com.squareup.moshi;
import java.io.IOException; import java.io.IOException;
import java.io.StringReader; import org.junit.Test;
import junit.framework.TestCase;
public class JsonReaderPathTest extends TestCase { import static org.junit.Assert.assertEquals;
public void testPath() throws IOException {
JsonReader reader = new JsonReader( public class JsonReaderPathTest {
new StringReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}")); @Test public void path() throws IOException {
JsonReader reader = new JsonReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}");
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.beginObject(); reader.beginObject();
assertEquals("$.", reader.getPath()); assertEquals("$.", reader.getPath());
@@ -60,8 +60,8 @@ public class JsonReaderPathTest extends TestCase {
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
public void testObjectPath() throws IOException { @Test public void objectPath() throws IOException {
JsonReader reader = new JsonReader(new StringReader("{\"a\":1,\"b\":2}")); JsonReader reader = new JsonReader("{\"a\":1,\"b\":2}");
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
@@ -100,8 +100,8 @@ public class JsonReaderPathTest extends TestCase {
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
public void testArrayPath() throws IOException { @Test public void arrayPath() throws IOException {
JsonReader reader = new JsonReader(new StringReader("[1,2]")); JsonReader reader = new JsonReader("[1,2]");
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
reader.peek(); reader.peek();
@@ -130,8 +130,8 @@ public class JsonReaderPathTest extends TestCase {
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
public void testMultipleTopLevelValuesInOneDocument() throws IOException { @Test public void multipleTopLevelValuesInOneDocument() throws IOException {
JsonReader reader = new JsonReader(new StringReader("[][]")); JsonReader reader = new JsonReader("[][]");
reader.setLenient(true); reader.setLenient(true);
reader.beginArray(); reader.beginArray();
reader.endArray(); reader.endArray();
@@ -141,23 +141,23 @@ public class JsonReaderPathTest extends TestCase {
assertEquals("$", reader.getPath()); assertEquals("$", reader.getPath());
} }
public void testSkipArrayElements() throws IOException { @Test public void skipArrayElements() throws IOException {
JsonReader reader = new JsonReader(new StringReader("[1,2,3]")); JsonReader reader = new JsonReader("[1,2,3]");
reader.beginArray(); reader.beginArray();
reader.skipValue(); reader.skipValue();
reader.skipValue(); reader.skipValue();
assertEquals("$[2]", reader.getPath()); assertEquals("$[2]", reader.getPath());
} }
public void testSkipObjectNames() throws IOException { @Test public void skipObjectNames() throws IOException {
JsonReader reader = new JsonReader(new StringReader("{\"a\":1}")); JsonReader reader = new JsonReader("{\"a\":1}");
reader.beginObject(); reader.beginObject();
reader.skipValue(); reader.skipValue();
assertEquals("$.null", reader.getPath()); assertEquals("$.null", reader.getPath());
} }
public void testSkipObjectValues() throws IOException { @Test public void skipObjectValues() throws IOException {
JsonReader reader = new JsonReader(new StringReader("{\"a\":1,\"b\":2}")); JsonReader reader = new JsonReader("{\"a\":1,\"b\":2}");
reader.beginObject(); reader.beginObject();
reader.nextName(); reader.nextName();
reader.skipValue(); reader.skipValue();
@@ -166,8 +166,8 @@ public class JsonReaderPathTest extends TestCase {
assertEquals("$.b", reader.getPath()); assertEquals("$.b", reader.getPath());
} }
public void testSkipNestedStructures() throws IOException { @Test public void skipNestedStructures() throws IOException {
JsonReader reader = new JsonReader(new StringReader("[[1,2,3],4]")); JsonReader reader = new JsonReader("[[1,2,3],4]");
reader.beginArray(); reader.beginArray();
reader.skipValue(); reader.skipValue();
assertEquals("$[1]", reader.getPath()); assertEquals("$[1]", reader.getPath());

File diff suppressed because it is too large Load Diff

View File

@@ -19,12 +19,36 @@ import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import junit.framework.TestCase; import org.junit.Test;
@SuppressWarnings("resource") import static org.junit.Assert.assertEquals;
public final class JsonWriterTest extends TestCase { import static org.junit.Assert.fail;
public void testWrongTopLevelType() throws IOException { public final class JsonWriterTest {
@Test public void nullsValuesNotSerializedByDefault() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject();
jsonWriter.name("a");
jsonWriter.nullValue();
jsonWriter.endObject();
jsonWriter.close();
assertEquals("{}", stringWriter.toString());
}
@Test public void nullsValuesSerializedWhenConfigured() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setSerializeNulls(true);
jsonWriter.beginObject();
jsonWriter.name("a");
jsonWriter.nullValue();
jsonWriter.endObject();
jsonWriter.close();
assertEquals("{\"a\":null}", stringWriter.toString());
}
@Test public void wrongTopLevelType() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
try { try {
@@ -34,7 +58,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testTwoNames() throws IOException { @Test public void twoNames() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -46,7 +70,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testNameWithoutValue() throws IOException { @Test public void nameWithoutValue() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -58,7 +82,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testValueWithoutName() throws IOException { @Test public void valueWithoutName() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -69,7 +93,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testMultipleTopLevelValues() throws IOException { @Test public void multipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray().endArray(); jsonWriter.beginArray().endArray();
@@ -80,7 +104,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testBadNestingObject() throws IOException { @Test public void badNestingObject() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -92,7 +116,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testBadNestingArray() throws IOException { @Test public void badNestingArray() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -104,7 +128,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testNullName() throws IOException { @Test public void nullName() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -115,9 +139,10 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testNullStringValue() throws IOException { @Test public void nullStringValue() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setSerializeNulls(true);
jsonWriter.beginObject(); jsonWriter.beginObject();
jsonWriter.name("a"); jsonWriter.name("a");
jsonWriter.value((String) null); jsonWriter.value((String) null);
@@ -125,7 +150,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("{\"a\":null}", stringWriter.toString()); assertEquals("{\"a\":null}", stringWriter.toString());
} }
public void testNonFiniteDoubles() throws IOException { @Test public void nonFiniteDoubles() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -146,7 +171,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testNonFiniteBoxedDoubles() throws IOException { @Test public void nonFiniteBoxedDoubles() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -167,7 +192,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testDoubles() throws IOException { @Test public void doubles() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -193,7 +218,7 @@ public final class JsonWriterTest extends TestCase {
+ "2.718281828459045]", stringWriter.toString()); + "2.718281828459045]", stringWriter.toString());
} }
public void testLongs() throws IOException { @Test public void longs() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -211,7 +236,7 @@ public final class JsonWriterTest extends TestCase {
+ "9223372036854775807]", stringWriter.toString()); + "9223372036854775807]", stringWriter.toString());
} }
public void testNumbers() throws IOException { @Test public void numbers() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -227,7 +252,7 @@ public final class JsonWriterTest extends TestCase {
+ "3.141592653589793238462643383]", stringWriter.toString()); + "3.141592653589793238462643383]", stringWriter.toString());
} }
public void testBooleans() throws IOException { @Test public void booleans() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -237,7 +262,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[true,false]", stringWriter.toString()); assertEquals("[true,false]", stringWriter.toString());
} }
public void testNulls() throws IOException { @Test public void nulls() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -246,7 +271,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[null]", stringWriter.toString()); assertEquals("[null]", stringWriter.toString());
} }
public void testStrings() throws IOException { @Test public void strings() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -289,7 +314,7 @@ public final class JsonWriterTest extends TestCase {
+ "\"\\u0019\"]", stringWriter.toString()); + "\"\\u0019\"]", stringWriter.toString());
} }
public void testUnicodeLineBreaksEscaped() throws IOException { @Test public void unicodeLineBreaksEscaped() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -298,7 +323,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[\"\\u2028 \\u2029\"]", stringWriter.toString()); assertEquals("[\"\\u2028 \\u2029\"]", stringWriter.toString());
} }
public void testEmptyArray() throws IOException { @Test public void emptyArray() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -306,7 +331,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[]", stringWriter.toString()); assertEquals("[]", stringWriter.toString());
} }
public void testEmptyObject() throws IOException { @Test public void emptyObject() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -314,7 +339,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("{}", stringWriter.toString()); assertEquals("{}", stringWriter.toString());
} }
public void testObjectsInArrays() throws IOException { @Test public void objectsInArrays() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginArray(); jsonWriter.beginArray();
@@ -331,7 +356,7 @@ public final class JsonWriterTest extends TestCase {
+ "{\"c\":6,\"d\":true}]", stringWriter.toString()); + "{\"c\":6,\"d\":true}]", stringWriter.toString());
} }
public void testArraysInObjects() throws IOException { @Test public void arraysInObjects() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -350,7 +375,7 @@ public final class JsonWriterTest extends TestCase {
+ "\"b\":[6,true]}", stringWriter.toString()); + "\"b\":[6,true]}", stringWriter.toString());
} }
public void testDeepNestingArrays() throws IOException { @Test public void deepNestingArrays() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
for (int i = 0; i < 20; i++) { for (int i = 0; i < 20; i++) {
@@ -362,7 +387,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString()); assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
} }
public void testDeepNestingObjects() throws IOException { @Test public void deepNestingObjects() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -379,7 +404,7 @@ public final class JsonWriterTest extends TestCase {
+ "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString()); + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
} }
public void testRepeatedName() throws IOException { @Test public void repeatedName() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -390,9 +415,10 @@ public final class JsonWriterTest extends TestCase {
assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString()); assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
} }
public void testPrettyPrintObject() throws IOException { @Test public void prettyPrintObject() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setSerializeNulls(true);
jsonWriter.setIndent(" "); jsonWriter.setIndent(" ");
jsonWriter.beginObject(); jsonWriter.beginObject();
@@ -427,7 +453,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals(expected, stringWriter.toString()); assertEquals(expected, stringWriter.toString());
} }
public void testPrettyPrintArray() throws IOException { @Test public void prettyPrintArray() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter); JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" "); jsonWriter.setIndent(" ");
@@ -464,7 +490,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals(expected, stringWriter.toString()); assertEquals(expected, stringWriter.toString());
} }
public void testLenientWriterPermitsMultipleTopLevelValues() throws IOException { @Test public void lenientWriterPermitsMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.setLenient(true); writer.setLenient(true);
@@ -476,7 +502,7 @@ public final class JsonWriterTest extends TestCase {
assertEquals("[][]", stringWriter.toString()); assertEquals("[][]", stringWriter.toString());
} }
public void testStrictWriterDoesNotPermitMultipleTopLevelValues() throws IOException { @Test public void strictWriterDoesNotPermitMultipleTopLevelValues() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();
@@ -488,7 +514,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testClosedWriterThrowsOnStructure() throws IOException { @Test public void closedWriterThrowsOnStructure() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();
@@ -516,7 +542,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testClosedWriterThrowsOnName() throws IOException { @Test public void closedWriterThrowsOnName() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();
@@ -529,7 +555,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testClosedWriterThrowsOnValue() throws IOException { @Test public void closedWriterThrowsOnValue() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();
@@ -542,7 +568,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testClosedWriterThrowsOnFlush() throws IOException { @Test public void closedWriterThrowsOnFlush() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();
@@ -555,7 +581,7 @@ public final class JsonWriterTest extends TestCase {
} }
} }
public void testWriterCloseIsIdempotent() throws IOException { @Test public void writerCloseIsIdempotent() throws IOException {
StringWriter stringWriter = new StringWriter(); StringWriter stringWriter = new StringWriter();
JsonWriter writer = new JsonWriter(stringWriter); JsonWriter writer = new JsonWriter(stringWriter);
writer.beginArray(); writer.beginArray();