diff options
author | Jesse Wilson <jessewilson@google.com> | 2010-11-18 23:09:44 -0800 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2010-11-18 23:09:44 -0800 |
commit | a8bbc11afc0f93143c1fd200108a51c95507cc43 (patch) | |
tree | de321466e42062867cf83ed9c53d1383765874e9 /core | |
parent | 60899fac2bf35086b0083bfbffa2f6bf5c18f2d9 (diff) | |
parent | 9d9b4e70a1e7f9ffb6cedd8a86fdd926f2a28202 (diff) | |
download | frameworks_base-a8bbc11afc0f93143c1fd200108a51c95507cc43.zip frameworks_base-a8bbc11afc0f93143c1fd200108a51c95507cc43.tar.gz frameworks_base-a8bbc11afc0f93143c1fd200108a51c95507cc43.tar.bz2 |
Merge "Decode JSON literal types eagerly and with our own decoder."
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/util/JsonReader.java | 346 | ||||
-rw-r--r-- | core/tests/coretests/src/android/util/JsonReaderTest.java | 159 |
2 files changed, 342 insertions, 163 deletions
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index d13d97a..8025545 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -169,6 +169,9 @@ import java.util.List; */ public final class JsonReader implements Closeable { + private static final String TRUE = "true"; + private static final String FALSE = "false"; + /** The input JSON. */ private final Reader in; @@ -178,6 +181,8 @@ public final class JsonReader implements Closeable { /** * Use a manual buffer to easily read and unread upcoming characters, and * also so we can create strings without an intermediate StringBuilder. + * 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; @@ -189,26 +194,21 @@ public final class JsonReader implements Closeable { } /** - * True if we've already read the next token. If we have, the string value - * for that token will be assigned to {@code value} if such a string value - * exists. And the token type will be assigned to {@code token} if the token - * type is known. The token type may be null for literals, since we derive - * that lazily. - */ - private boolean hasToken; - - /** * The type of the next token to be returned by {@link #peek} and {@link - * #advance}, or {@code null} if it is unknown and must first be derived - * from {@code value}. This value is undefined if {@code hasToken} is false. + * #advance}. If null, peek() will assign a value. */ private JsonToken token; /** The text of the next name. */ private String name; - /** The text of the next literal value. */ + /* + * For the next literal value, we may have the text value, or the position + * and length in the buffer. + */ private String value; + private int valuePos; + private int valueLength; /** True if we're currently handling a skipValue() call. */ private boolean skipping = false; @@ -284,7 +284,7 @@ public final class JsonReader implements Closeable { * Consumes {@code expected}. */ private void expect(JsonToken expected) throws IOException { - quickPeek(); + peek(); if (token != expected) { throw new IllegalStateException("Expected " + expected + " but was " + peek()); } @@ -295,7 +295,7 @@ public final class JsonReader implements Closeable { * Returns true if the current array or object has another element. */ public boolean hasNext() throws IOException { - quickPeek(); + peek(); return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY; } @@ -303,23 +303,8 @@ public final class JsonReader implements Closeable { * Returns the type of the next token without consuming it. */ public JsonToken peek() throws IOException { - quickPeek(); - - if (token == null) { - decodeLiteral(); - } - - return token; - } - - /** - * Ensures that a token is ready. After this call either {@code token} or - * {@code value} will be non-null. To ensure {@code token} has a definitive - * value, use {@link #peek()} - */ - private JsonToken quickPeek() throws IOException { - if (hasToken) { - return token; + if (token != null) { + return token; } switch (peekStack()) { @@ -342,7 +327,6 @@ public final class JsonReader implements Closeable { case NONEMPTY_OBJECT: return nextInObject(false); case NONEMPTY_DOCUMENT: - hasToken = true; return token = JsonToken.END_DOCUMENT; case CLOSED: throw new IllegalStateException("JsonReader is closed"); @@ -355,10 +339,9 @@ public final class JsonReader implements Closeable { * Advances the cursor in the JSON stream to the next token. */ private JsonToken advance() throws IOException { - quickPeek(); + peek(); JsonToken result = token; - hasToken = false; token = null; value = null; name = null; @@ -373,7 +356,7 @@ public final class JsonReader implements Closeable { * name. */ public String nextName() throws IOException { - quickPeek(); + peek(); if (token != JsonToken.NAME) { throw new IllegalStateException("Expected a name but was " + peek()); } @@ -392,7 +375,7 @@ public final class JsonReader implements Closeable { */ public String nextString() throws IOException { peek(); - if (value == null || (token != JsonToken.STRING && token != JsonToken.NUMBER)) { + if (token != JsonToken.STRING && token != JsonToken.NUMBER) { throw new IllegalStateException("Expected a string but was " + peek()); } @@ -409,20 +392,12 @@ public final class JsonReader implements Closeable { * this reader is closed. */ public boolean nextBoolean() throws IOException { - quickPeek(); - if (value == null || token == JsonToken.STRING) { - throw new IllegalStateException("Expected a boolean but was " + peek()); - } - - boolean result; - if (value.equalsIgnoreCase("true")) { - result = true; - } else if (value.equalsIgnoreCase("false")) { - result = false; - } else { - throw new IllegalStateException("Not a boolean: " + value); + peek(); + if (token != JsonToken.BOOLEAN) { + throw new IllegalStateException("Expected a boolean but was " + token); } + boolean result = (value == TRUE); advance(); return result; } @@ -435,13 +410,9 @@ public final class JsonReader implements Closeable { * reader is closed. */ public void nextNull() throws IOException { - quickPeek(); - if (value == null || token == JsonToken.STRING) { - throw new IllegalStateException("Expected null but was " + peek()); - } - - if (!value.equalsIgnoreCase("null")) { - throw new IllegalStateException("Not a null: " + value); + peek(); + if (token != JsonToken.NULL) { + throw new IllegalStateException("Expected null but was " + token); } advance(); @@ -450,27 +421,17 @@ public final class JsonReader implements Closeable { /** * Returns the {@link JsonToken#NUMBER double} value of the next token, * consuming it. If the next token is a string, this method will attempt to - * parse it as a double. + * parse it as a double using {@link Double#parseDouble(String)}. * * @throws IllegalStateException if the next token is not a literal value. - * @throws NumberFormatException if the next literal value cannot be parsed - * as a double, or is non-finite. */ public double nextDouble() throws IOException { - quickPeek(); - if (value == null) { - throw new IllegalStateException("Expected a double but was " + peek()); + peek(); + if (token != JsonToken.STRING && token != JsonToken.NUMBER) { + throw new IllegalStateException("Expected a double but was " + token); } double result = Double.parseDouble(value); - - if ((result >= 1.0d && value.startsWith("0")) - || Double.isNaN(result) - || Double.isInfinite(result)) { - throw new NumberFormatException( - "JSON forbids octal prefixes, NaN and infinities: " + value); - } - advance(); return result; } @@ -486,9 +447,9 @@ public final class JsonReader implements Closeable { * as a number, or exactly represented as a long. */ public long nextLong() throws IOException { - quickPeek(); - if (value == null) { - throw new IllegalStateException("Expected a long but was " + peek()); + peek(); + if (token != JsonToken.STRING && token != JsonToken.NUMBER) { + throw new IllegalStateException("Expected a long but was " + token); } long result; @@ -502,10 +463,6 @@ public final class JsonReader implements Closeable { } } - if (result >= 1L && value.startsWith("0")) { - throw new NumberFormatException("JSON forbids octal prefixes: " + value); - } - advance(); return result; } @@ -521,9 +478,9 @@ public final class JsonReader implements Closeable { * as a number, or exactly represented as an int. */ public int nextInt() throws IOException { - quickPeek(); - if (value == null) { - throw new IllegalStateException("Expected an int but was " + peek()); + peek(); + if (token != JsonToken.STRING && token != JsonToken.NUMBER) { + throw new IllegalStateException("Expected an int but was " + token); } int result; @@ -537,10 +494,6 @@ public final class JsonReader implements Closeable { } } - if (result >= 1L && value.startsWith("0")) { - throw new NumberFormatException("JSON forbids octal prefixes: " + value); - } - advance(); return result; } @@ -549,7 +502,6 @@ public final class JsonReader implements Closeable { * Closes this JSON reader and the underlying {@link Reader}. */ public void close() throws IOException { - hasToken = false; value = null; token = null; stack.clear(); @@ -606,7 +558,6 @@ public final class JsonReader implements Closeable { switch (nextNonWhitespace()) { case ']': pop(); - hasToken = true; return token = JsonToken.END_ARRAY; case ';': checkLenient(); // fall-through @@ -621,7 +572,6 @@ public final class JsonReader implements Closeable { case ']': if (firstElement) { pop(); - hasToken = true; return token = JsonToken.END_ARRAY; } // fall-through to handle ",]" @@ -630,7 +580,6 @@ public final class JsonReader implements Closeable { /* In lenient mode, a 0-length literal means 'null' */ checkLenient(); pos--; - hasToken = true; value = "null"; return token = JsonToken.NULL; default: @@ -650,7 +599,6 @@ public final class JsonReader implements Closeable { switch (nextNonWhitespace()) { case '}': pop(); - hasToken = true; return token = JsonToken.END_OBJECT; default: pos--; @@ -659,7 +607,6 @@ public final class JsonReader implements Closeable { switch (nextNonWhitespace()) { case '}': pop(); - hasToken = true; return token = JsonToken.END_OBJECT; case ';': case ',': @@ -680,14 +627,13 @@ public final class JsonReader implements Closeable { default: checkLenient(); pos--; - name = nextLiteral(); + name = nextLiteral(false); if (name.isEmpty()) { throw syntaxError("Expected name"); } } replaceTop(JsonScope.DANGLING_NAME); - hasToken = true; return token = JsonToken.NAME; } @@ -718,19 +664,16 @@ public final class JsonReader implements Closeable { switch (c) { case '{': push(JsonScope.EMPTY_OBJECT); - hasToken = true; return token = JsonToken.BEGIN_OBJECT; case '[': push(JsonScope.EMPTY_ARRAY); - hasToken = true; return token = JsonToken.BEGIN_ARRAY; case '\'': checkLenient(); // fall-through case '"': value = nextString((char) c); - hasToken = true; return token = JsonToken.STRING; default: @@ -899,54 +842,86 @@ public final class JsonReader implements Closeable { } /** - * Returns the string up to but not including any delimiter characters. This + * Reads the value up to but not including any delimiter characters. This * does not consume the delimiter character. + * + * @param assignOffsetsOnly true for this method to only set the valuePos + * and valueLength fields and return a null result. This only works if + * the literal is short; a string is returned otherwise. */ - private String nextLiteral() throws IOException { + private String nextLiteral(boolean assignOffsetsOnly) throws IOException { StringBuilder builder = null; - do { - /* the index of the first character not yet appended to the builder. */ - int start = pos; - while (pos < limit) { - int c = buffer[pos++]; - switch (c) { - case '/': - case '\\': - case ';': - case '#': - case '=': - checkLenient(); // fall-through - - case '{': - case '}': - case '[': - case ']': - case ':': - case ',': - case ' ': - case '\t': - case '\f': - case '\r': - case '\n': - pos--; - if (skipping) { - return "skipped!"; - } else if (builder == null) { - return new String(buffer, start, pos - start); - } else { - builder.append(buffer, start, pos - start); - return builder.toString(); - } + valuePos = -1; + valueLength = 0; + int i = 0; + + findNonLiteralCharacter: + while (true) { + 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': + break findNonLiteralCharacter; + } + } + + /* + * Attempt to load the entire literal into the buffer at once. If + * we run out of input, add a non-literal character at the end so + * that decoding doesn't need to do bounds checks. + */ + if (i < buffer.length) { + if (fillBuffer(i + 1)) { + continue; + } else { + buffer[limit] = '\0'; + break; } } + // use a StringBuilder when the value is too long. It must be an unquoted string. if (builder == null) { builder = new StringBuilder(); } - builder.append(buffer, start, pos - start); - } while (fillBuffer(1)); + builder.append(buffer, pos, i); + valueLength += i; + pos += i; + i = 0; + if (!fillBuffer(1)) { + break; + } + } - return builder.toString(); + String result; + if (assignOffsetsOnly && builder == null) { + valuePos = pos; + result = null; + } else if (skipping) { + result = "skipped!"; + } else if (builder == null) { + result = new String(buffer, pos, i); + } else { + builder.append(buffer, pos, i); + result = builder.toString(); + } + valueLength += i; + pos += i; + return result; } @Override public String toString() { @@ -1004,32 +979,105 @@ public final class JsonReader implements Closeable { * Reads a null, boolean, numeric or unquoted string literal value. */ private JsonToken readLiteral() throws IOException { - String literal = nextLiteral(); - if (literal.isEmpty()) { + value = nextLiteral(true); + if (valueLength == 0) { throw syntaxError("Expected literal value"); } - value = literal; - hasToken = true; - return token = null; // use decodeLiteral() to get the token type + token = decodeLiteral(); + if (token == JsonToken.STRING) { + checkLenient(); + } + return token; } /** * Assigns {@code nextToken} based on the value of {@code nextValue}. */ - private void decodeLiteral() throws IOException { - if (value.equalsIgnoreCase("null")) { - token = JsonToken.NULL; - } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { - token = JsonToken.BOOLEAN; + private JsonToken decodeLiteral() throws IOException { + if (valuePos == -1) { + // it was too long to fit in the buffer so it can only be a string + return JsonToken.STRING; + } else if (valueLength == 4 + && ('n' == buffer[valuePos ] || 'N' == buffer[valuePos ]) + && ('u' == buffer[valuePos + 1] || 'U' == buffer[valuePos + 1]) + && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) + && ('l' == buffer[valuePos + 3] || 'L' == buffer[valuePos + 3])) { + value = "null"; + return JsonToken.NULL; + } else if (valueLength == 4 + && ('t' == buffer[valuePos ] || 'T' == buffer[valuePos ]) + && ('r' == buffer[valuePos + 1] || 'R' == buffer[valuePos + 1]) + && ('u' == buffer[valuePos + 2] || 'U' == buffer[valuePos + 2]) + && ('e' == buffer[valuePos + 3] || 'E' == buffer[valuePos + 3])) { + value = TRUE; + return JsonToken.BOOLEAN; + } else if (valueLength == 5 + && ('f' == buffer[valuePos ] || 'F' == buffer[valuePos ]) + && ('a' == buffer[valuePos + 1] || 'A' == buffer[valuePos + 1]) + && ('l' == buffer[valuePos + 2] || 'L' == buffer[valuePos + 2]) + && ('s' == buffer[valuePos + 3] || 'S' == buffer[valuePos + 3]) + && ('e' == buffer[valuePos + 4] || 'E' == buffer[valuePos + 4])) { + value = FALSE; + return JsonToken.BOOLEAN; } else { - try { - Double.parseDouble(value); // this work could potentially be cached - token = JsonToken.NUMBER; - } catch (NumberFormatException ignored) { - // this must be an unquoted string - checkLenient(); - token = JsonToken.STRING; + value = new String(buffer, valuePos, valueLength); + return decodeNumber(buffer, valuePos, valueLength); + } + } + + /** + * Determine whether the characters is a JSON number. Numbers are of the + * form -12.34e+56. Fractional and exponential parts are optional. Leading + * zeroes are not allowed in the value or exponential part, but are allowed + * in the fraction. + * + * <p>This has a side effect of setting isInteger. + */ + private JsonToken decodeNumber(char[] chars, int offset, int length) { + int i = offset; + int c = chars[i]; + + if (c == '-') { + c = chars[++i]; + } + + if (c == '0') { + c = chars[++i]; + } else if (c >= '1' && c <= '9') { + c = chars[++i]; + while (c >= '0' && c <= '9') { + c = chars[++i]; + } + } else { + return JsonToken.STRING; + } + + if (c == '.') { + c = chars[++i]; + while (c >= '0' && c <= '9') { + c = chars[++i]; + } + } + + if (c == 'e' || c == 'E') { + c = chars[++i]; + if (c == '+' || c == '-') { + c = chars[++i]; } + if (c >= '0' && c <= '9') { + c = chars[++i]; + while (c >= '0' && c <= '9') { + c = chars[++i]; + } + } else { + return JsonToken.STRING; + } + } + + if (i == offset + length) { + return JsonToken.NUMBER; + } else { + return JsonToken.STRING; } } diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java index ced9310..216a772 100644 --- a/core/tests/coretests/src/android/util/JsonReaderTest.java +++ b/core/tests/coretests/src/android/util/JsonReaderTest.java @@ -16,6 +16,7 @@ package android.util; +import java.util.Arrays; import junit.framework.TestCase; import java.io.IOException; @@ -23,6 +24,8 @@ import java.io.StringReader; public final class JsonReaderTest extends TestCase { + private static final int READER_BUFFER_SIZE = 1024; + public void testReadArray() throws IOException { JsonReader reader = new JsonReader(new StringReader("[true, true]")); reader.beginArray(); @@ -178,7 +181,13 @@ public final class JsonReaderTest extends TestCase { + "-0.5," + "2.2250738585072014E-308," + "3.141592653589793," - + "2.718281828459045]"; + + "2.718281828459045," + + "\"1.0\"," + + "\"011.0\"," + + "\"NaN\"," + + "\"Infinity\"," + + "\"-Infinity\"" + + "]"; JsonReader reader = new JsonReader(new StringReader(json)); reader.beginArray(); assertEquals(-0.0, reader.nextDouble()); @@ -190,19 +199,98 @@ public final class JsonReaderTest extends TestCase { assertEquals(2.2250738585072014E-308, reader.nextDouble()); assertEquals(3.141592653589793, reader.nextDouble()); assertEquals(2.718281828459045, reader.nextDouble()); + assertEquals(1,0, reader.nextDouble()); + assertEquals(11.0, reader.nextDouble()); + assertTrue(Double.isNaN(reader.nextDouble())); + assertEquals(Double.POSITIVE_INFINITY, reader.nextDouble()); + assertEquals(Double.NEGATIVE_INFINITY, reader.nextDouble()); reader.endArray(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } - public void testNonFiniteDoubles() throws IOException { - String json = "[NaN]"; + public void testLenientDoubles() throws IOException { + String json = "[" + + "011.0," + + "NaN," + + "NAN," + + "Infinity," + + "INFINITY," + + "-Infinity" + + "]"; JsonReader reader = new JsonReader(new StringReader(json)); + reader.setLenient(true); reader.beginArray(); + assertEquals(11.0, reader.nextDouble()); + assertTrue(Double.isNaN(reader.nextDouble())); + try { + reader.nextDouble(); + fail(); + } catch (NumberFormatException expected) { + } + assertEquals("NAN", reader.nextString()); + assertEquals(Double.POSITIVE_INFINITY, reader.nextDouble()); try { reader.nextDouble(); fail(); } catch (NumberFormatException expected) { } + assertEquals("INFINITY", reader.nextString()); + assertEquals(Double.NEGATIVE_INFINITY, reader.nextDouble()); + reader.endArray(); + assertEquals(JsonToken.END_DOCUMENT, reader.peek()); + } + + public void testBufferBoundary() throws IOException { + char[] pad = new char[READER_BUFFER_SIZE - 8]; + Arrays.fill(pad, '5'); + String json = "[\"" + new String(pad) + "\",33333]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.beginArray(); + assertEquals(JsonToken.STRING, reader.peek()); + assertEquals(new String(pad), reader.nextString()); + assertEquals(JsonToken.NUMBER, reader.peek()); + assertEquals(33333, reader.nextInt()); + } + + public void testTruncatedBufferBoundary() throws IOException { + char[] pad = new char[READER_BUFFER_SIZE - 8]; + Arrays.fill(pad, '5'); + String json = "[\"" + new String(pad) + "\",33333"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.setLenient(true); + reader.beginArray(); + assertEquals(JsonToken.STRING, reader.peek()); + assertEquals(new String(pad), reader.nextString()); + assertEquals(JsonToken.NUMBER, reader.peek()); + assertEquals(33333, reader.nextInt()); + try { + reader.endArray(); + fail(); + } catch (IOException e) { + } + } + + public void testLongestSupportedNumericLiterals() throws IOException { + testLongNumericLiterals(READER_BUFFER_SIZE - 1, JsonToken.NUMBER); + } + + public void testLongerNumericLiterals() throws IOException { + testLongNumericLiterals(READER_BUFFER_SIZE, JsonToken.STRING); + } + + private void testLongNumericLiterals(int length, JsonToken expectedToken) throws IOException { + char[] longNumber = new char[length]; + Arrays.fill(longNumber, '9'); + longNumber[0] = '1'; + longNumber[1] = '.'; + + String json = "[" + new String(longNumber) + "]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.setLenient(true); + reader.beginArray(); + assertEquals(expectedToken, reader.peek()); + assertEquals(2.0d, reader.nextDouble()); + reader.endArray(); } public void testLongs() throws IOException { @@ -210,7 +298,13 @@ public final class JsonReaderTest extends TestCase { + "1,1,1," + "-1,-1,-1," + "-9223372036854775808," - + "9223372036854775807]"; + + "9223372036854775807," + + "5.0," + + "1.0e2," + + "\"011\"," + + "\"5.0\"," + + "\"1.0e2\"" + + "]"; JsonReader reader = new JsonReader(new StringReader(json)); reader.beginArray(); assertEquals(0L, reader.nextLong()); @@ -234,6 +328,11 @@ public final class JsonReaderTest extends TestCase { } catch (NumberFormatException expected) { } assertEquals(Long.MAX_VALUE, reader.nextLong()); + assertEquals(5, reader.nextLong()); + assertEquals(100, reader.nextLong()); + assertEquals(11, reader.nextLong()); + assertEquals(5, reader.nextLong()); + assertEquals(100, reader.nextLong()); reader.endArray(); assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } @@ -250,28 +349,60 @@ public final class JsonReaderTest extends TestCase { reader.endArray(); } - public void testNumberWithOctalPrefix() throws IOException { - String json = "[01]"; + public void testMatchingValidNumbers() throws IOException { + String json = "[-1,99,-0,0,0e1,0e+1,0e-1,0E1,0E+1,0E-1,0.0,1.0,-1.0,1.0e0,1.0e+1,1.0e-1]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.beginArray(); + for (int i = 0; i < 16; i++) { + assertEquals(JsonToken.NUMBER, reader.peek()); + reader.nextDouble(); + } + reader.endArray(); + } + + public void testRecognizingInvalidNumbers() throws IOException { + String json = "[-00,00,001,+1,1f,0x,0xf,0x0,0f1,0ee1,1..0,1e0.1,1.-01,1.+1,1.0x,1.0+]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.setLenient(true); + reader.beginArray(); + for (int i = 0; i < 16; i++) { + assertEquals(JsonToken.STRING, reader.peek()); + reader.nextString(); + } + reader.endArray(); + } + + public void testNonFiniteDouble() throws IOException { + String json = "[NaN]"; JsonReader reader = new JsonReader(new StringReader(json)); reader.beginArray(); try { - reader.nextInt(); + reader.nextDouble(); fail(); - } catch (NumberFormatException expected) { + } catch (IOException expected) { } + } + + public void testNumberWithHexPrefix() throws IOException { + String json = "[0x11]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.beginArray(); try { reader.nextLong(); fail(); - } catch (NumberFormatException expected) { + } catch (IOException expected) { } + } + + public void testNumberWithOctalPrefix() throws IOException { + String json = "[01]"; + JsonReader reader = new JsonReader(new StringReader(json)); + reader.beginArray(); try { - reader.nextDouble(); + reader.nextInt(); fail(); - } catch (NumberFormatException expected) { + } catch (IOException expected) { } - assertEquals("01", reader.nextString()); - reader.endArray(); - assertEquals(JsonToken.END_DOCUMENT, reader.peek()); } public void testBooleans() throws IOException { |