Restore fast skips.
document api ns linear runtime
TWEETS GSON_STREAM 397568 =========
TWEETS GSON_SKIP 300058 =======
READER_SHORT GSON_STREAM 76632 =
READER_SHORT GSON_SKIP 57796 =
READER_LONG GSON_STREAM 894690 =====================
READER_LONG GSON_SKIP 565114 =============
diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java
index c7be577..9649230 100644
--- a/gson/src/main/java/com/google/gson/stream/JsonReader.java
+++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java
@@ -202,6 +202,7 @@
private static final int PEEKED_SINGLE_QUOTED = 8;
private static final int PEEKED_DOUBLE_QUOTED = 9;
private static final int PEEKED_UNQUOTED = 10;
+ /** When this is returned, the string value is stored in peekedString. */
private static final int PEEKED_BUFFERED = 11;
private static final int PEEKED_SINGLE_QUOTED_NAME = 12;
private static final int PEEKED_DOUBLE_QUOTED_NAME = 13;
@@ -242,10 +243,16 @@
private long peekedInteger;
/**
- * The number of characters in the peeked number.
+ * The number of characters in a peeked number literal. Increment 'pos' by
+ * this after reading a number.
*/
private int peekedNumberLength;
+ /**
+ * A peeked string that should be parsed on the next double, long or string.
+ * This is populated before a numeric value is parsed and used if that parsing
+ * fails.
+ */
private String peekedString;
/*
@@ -945,9 +952,7 @@
if (c == quote) {
pos = p;
- if (false /* TODO: fast skipping */) {
- return "skipped!";
- } else if (builder == null) {
+ if (builder == null) {
return new String(buffer, start, p - start - 1);
} else {
builder.append(buffer, start, p - start - 1);
@@ -1033,9 +1038,7 @@
}
String result;
- if (false /* TODO: fast skipping */) {
- result = "skipped!";
- } else if (builder == null) {
+ if (builder == null) {
result = new String(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
@@ -1045,6 +1048,60 @@
return result;
}
+ private void skipQuotedValue(char quote) throws IOException {
+ // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
+ char[] buffer = this.buffer;
+ do {
+ 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) {
+ pos = p;
+ return;
+ } else if (c == '\\') {
+ pos = p;
+ readEscapeCharacter();
+ p = pos;
+ l = limit;
+ }
+ }
+ pos = p;
+ } while (fillBuffer(1));
+ throw syntaxError("Unterminated string");
+ }
+
+ private void skipUnquotedValue() throws IOException {
+ do {
+ int i = 0;
+ for (; pos + i < limit; i++) {
+ switch (buffer[pos + i]) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ pos += i;
+ return;
+ }
+ }
+ pos += i;
+ } while (fillBuffer(1));
+ }
+
/**
* Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the next token,
* consuming it. If the next token is a string, this method will attempt to
@@ -1133,11 +1190,11 @@
stackSize--;
count--;
} else if (p == PEEKED_UNQUOTED_NAME || p == PEEKED_UNQUOTED) {
- nextUnquotedValue();
+ skipUnquotedValue();
} else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_SINGLE_QUOTED_NAME) {
- nextQuotedValue('\'');
+ skipQuotedValue('\'');
} else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) {
- nextQuotedValue('"');
+ skipQuotedValue('"');
} else if (p == PEEKED_NUMBER) {
pos += peekedNumberLength;
}
diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
index f8c7774..cddec2b 100644
--- a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
+++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
@@ -1172,6 +1172,24 @@
"[\n\n" + spaces + "\n\n\n0,}]");
}
+ public void disabled_testVeryLongNumber() throws IOException {
+ // TODO: this is a completely broken case that needs to be fixed!
+ JsonReader reader = new JsonReader(new StringReader("[0." + repeat('9', 8192) + "]"));
+ reader.beginArray();
+ assertEquals(1d, reader.nextDouble());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testVeryLongUnquotedLiteral() throws IOException {
+ String literal = "a" + repeat('b', 8192) + "c";
+ JsonReader reader = new JsonReader(new StringReader("[" + literal + "]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(literal, reader.nextString());
+ reader.endArray();
+ }
+
public void testFailWithPositionIsOffsetByBom() throws IOException {
testFailWithPosition("Expected value at line 1 column 4",
"\ufeff[0,}]");
@@ -1306,17 +1324,34 @@
}
public void testSkipVeryLongUnquotedString() throws IOException {
- char[] stringChars = new char[1024 * 16];
- Arrays.fill(stringChars, 'x');
- String string = new String(stringChars);
- String json = "[" + string + "]";
- JsonReader reader = new JsonReader(new StringReader(json));
+ JsonReader reader = new JsonReader(new StringReader("[" + repeat('x', 8192) + "]"));
reader.setLenient(true);
reader.beginArray();
reader.skipValue();
reader.endArray();
}
+ public void testSkipTopLevelUnquotedString() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(repeat('x', 8192)));
+ reader.setLenient(true);
+ reader.skipValue();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipVeryLongQuotedString() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[\"" + repeat('x', 8192) + "\"]"));
+ reader.beginArray();
+ reader.skipValue();
+ reader.endArray();
+ }
+
+ public void testSkipTopLevelQuotedString() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("\"" + repeat('x', 8192) + "\""));
+ reader.setLenient(true);
+ reader.skipValue();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
public void testStringAsNumberWithTruncatedExponent() throws IOException {
JsonReader reader = new JsonReader(new StringReader("[123e]"));
reader.setLenient(true);
diff --git a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
index 59b9e0f..3007706 100644
--- a/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
+++ b/metrics/src/main/java/com/google/gson/metrics/ParseBenchmark.java
@@ -81,6 +81,11 @@
return new GsonStreamParser();
}
},
+ GSON_SKIP {
+ @Override Parser newParser() {
+ return new GsonSkipParser();
+ }
+ },
GSON_DOM {
@Override Parser newParser() {
return new GsonDomParser();
@@ -184,6 +189,15 @@
}
}
+ private static class GsonSkipParser implements Parser {
+ public void parse(char[] data, Document document) throws Exception {
+ com.google.gson.stream.JsonReader jsonReader
+ = new com.google.gson.stream.JsonReader(new CharArrayReader(data));
+ jsonReader.skipValue();
+ jsonReader.close();
+ }
+ }
+
private static class JacksonStreamParser implements Parser {
public void parse(char[] data, Document document) throws Exception {
JsonFactory jsonFactory = new JsonFactory();