diff --git a/api/current.xml b/api/current.xml index ad51bdaec70de..d70ba5d0182b9 100644 --- a/api/current.xml +++ b/api/current.xml @@ -200049,6 +200049,17 @@ + + - - - - - - + + + + + + + + + + + + + + + + + + - + diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java index 80255452ca6ec..8f44895bab01e 100644 --- a/core/java/android/util/JsonReader.java +++ b/core/java/android/util/JsonReader.java @@ -16,6 +16,7 @@ package android.util; +import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.io.Closeable; @@ -248,6 +249,13 @@ public final class JsonReader implements Closeable { this.lenient = lenient; } + /** + * Returns true if this parser is liberal in what it accepts. + */ + public boolean isLenient() { + return lenient; + } + /** * Consumes the next token from the JSON stream and asserts that it is the * beginning of a new array. @@ -311,7 +319,7 @@ public final class JsonReader implements Closeable { case EMPTY_DOCUMENT: replaceTop(JsonScope.NONEMPTY_DOCUMENT); JsonToken firstToken = nextValue(); - if (token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { + if (!lenient && token != JsonToken.BEGIN_ARRAY && token != JsonToken.BEGIN_OBJECT) { throw new IOException( "Expected JSON document to start with '[' or '{' but was " + token); } @@ -327,7 +335,15 @@ public final class JsonReader implements Closeable { case NONEMPTY_OBJECT: return nextInObject(false); case NONEMPTY_DOCUMENT: - return token = JsonToken.END_DOCUMENT; + try { + JsonToken token = nextValue(); + if (lenient) { + return token; + } + throw syntaxError("Expected EOF"); + } catch (EOFException e) { + return token = JsonToken.END_DOCUMENT; // TODO: avoid throwing here? + } case CLOSED: throw new IllegalStateException("JsonReader is closed"); default: @@ -758,7 +774,7 @@ public final class JsonReader implements Closeable { } } - throw syntaxError("End of input"); + throw new EOFException("End of input"); } private void checkLenient() throws IOException { @@ -1030,8 +1046,6 @@ public final class JsonReader implements Closeable { * 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. - * - *

This has a side effect of setting isInteger. */ private JsonToken decodeNumber(char[] chars, int offset, int length) { int i = offset; @@ -1085,8 +1099,8 @@ public final class JsonReader implements Closeable { * Throws a new IO exception with the given message and a context snippet * with this reader's content. */ - public IOException syntaxError(String message) throws IOException { - throw new JsonSyntaxException(message + " near " + getSnippet()); + private IOException syntaxError(String message) throws IOException { + throw new MalformedJsonException(message + " near " + getSnippet()); } private CharSequence getSnippet() { @@ -1097,10 +1111,4 @@ public final class JsonReader implements Closeable { snippet.append(buffer, pos, afterPos); return snippet; } - - private static class JsonSyntaxException extends IOException { - private JsonSyntaxException(String s) { - super(s); - } - } } diff --git a/core/java/android/util/JsonWriter.java b/core/java/android/util/JsonWriter.java index 89cad79eb8589..47e84c51dd253 100644 --- a/core/java/android/util/JsonWriter.java +++ b/core/java/android/util/JsonWriter.java @@ -138,6 +138,8 @@ public final class JsonWriter implements Closeable { */ private String separator = ":"; + private boolean lenient; + /** * Creates a new instance that writes a JSON-encoded stream to {@code out}. * For best performance, ensure {@link Writer} is buffered; wrapping in @@ -168,6 +170,29 @@ public final class JsonWriter implements Closeable { } } + /** + * Configure this writer to relax its syntax rules. By default, this writer + * only emits well-formed JSON as specified by RFC 4627. Setting the writer + * to lenient permits the following: + *

+ */ + public void setLenient(boolean lenient) { + this.lenient = lenient; + } + + /** + * Returns true if this writer has relaxed syntax rules. + */ + public boolean isLenient() { + return lenient; + } + /** * Begins encoding a new array. Each call to this method must be paired with * a call to {@link #endArray}. @@ -306,11 +331,11 @@ public final class JsonWriter implements Closeable { * Encodes {@code value}. * * @param value a finite value. May not be {@link Double#isNaN() NaNs} or - * {@link Double#isInfinite() infinities}. + * {@link Double#isInfinite() infinities} unless this writer is lenient. * @return this writer. */ public JsonWriter value(double value) throws IOException { - if (Double.isNaN(value) || Double.isInfinite(value)) { + if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) { throw new IllegalArgumentException("Numeric values must be finite, but was " + value); } beforeValue(false); @@ -329,6 +354,28 @@ public final class JsonWriter implements Closeable { return this; } + /** + * Encodes {@code value}. + * + * @param value a finite value. May not be {@link Double#isNaN() NaNs} or + * {@link Double#isInfinite() infinities} unless this writer is lenient. + * @return this writer. + */ + public JsonWriter value(Number value) throws IOException { + if (value == null) { + return nullValue(); + } + + String string = value.toString(); + if (!lenient && + (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) { + throw new IllegalArgumentException("Numeric values must be finite, but was " + value); + } + beforeValue(false); + out.append(string); + return this; + } + /** * Ensures all buffered data is written to the underlying {@link Writer} * and flushes that writer. @@ -364,7 +411,6 @@ public final class JsonWriter implements Closeable { switch (c) { case '"': case '\\': - case '/': out.write('\\'); out.write(c); break; @@ -439,7 +485,7 @@ public final class JsonWriter implements Closeable { private void beforeValue(boolean root) throws IOException { switch (peek()) { case EMPTY_DOCUMENT: // first in document - if (!root) { + if (!lenient && !root) { throw new IllegalStateException( "JSON must start with an array or an object."); } diff --git a/core/java/android/util/MalformedJsonException.java b/core/java/android/util/MalformedJsonException.java new file mode 100644 index 0000000000000..63c19ff86cd4e --- /dev/null +++ b/core/java/android/util/MalformedJsonException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import java.io.IOException; + +/** + * Thrown when a reader encounters malformed JSON. Some syntax errors can be + * ignored by calling {@link JsonReader#setLenient(boolean)}. + */ +public final class MalformedJsonException extends IOException { + private static final long serialVersionUID = 1L; + + public MalformedJsonException(String message) { + super(message); + } +} diff --git a/core/tests/coretests/src/android/util/JsonReaderTest.java b/core/tests/coretests/src/android/util/JsonReaderTest.java index 216a772dc4121..b5c2c2743f333 100644 --- a/core/tests/coretests/src/android/util/JsonReaderTest.java +++ b/core/tests/coretests/src/android/util/JsonReaderTest.java @@ -16,11 +16,10 @@ package android.util; -import java.util.Arrays; -import junit.framework.TestCase; - import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; +import junit.framework.TestCase; public final class JsonReaderTest extends TestCase { @@ -677,7 +676,7 @@ public final class JsonReaderTest extends TestCase { try { reader.nextString(); fail(); - } catch (IOException expected) { + } catch (MalformedJsonException expected) { } } @@ -811,4 +810,50 @@ public final class JsonReaderTest extends TestCase { reader.nextNull(); reader.endArray(); } + + public void testStrictMultipleTopLevelValues() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[] []")); + reader.beginArray(); + reader.endArray(); + try { + reader.peek(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientMultipleTopLevelValues() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[] true {}")); + reader.setLenient(true); + reader.beginArray(); + reader.endArray(); + assertEquals(true, reader.nextBoolean()); + reader.beginObject(); + reader.endObject(); + assertEquals(JsonToken.END_DOCUMENT, reader.peek()); + } + + public void testStrictTopLevelValueType() { + JsonReader reader = new JsonReader(new StringReader("true")); + try { + reader.nextBoolean(); + fail(); + } catch (IOException expected) { + } + } + + public void testLenientTopLevelValueType() throws IOException { + JsonReader reader = new JsonReader(new StringReader("true")); + reader.setLenient(true); + assertEquals(true, reader.nextBoolean()); + } + + public void testStrictNonExecutePrefix() { + JsonReader reader = new JsonReader(new StringReader(")]}'\n []")); + try { + reader.beginArray(); + fail(); + } catch (IOException expected) { + } + } } diff --git a/core/tests/coretests/src/android/util/JsonWriterTest.java b/core/tests/coretests/src/android/util/JsonWriterTest.java index fa840239d7b80..b29e2fdde08ca 100644 --- a/core/tests/coretests/src/android/util/JsonWriterTest.java +++ b/core/tests/coretests/src/android/util/JsonWriterTest.java @@ -16,10 +16,11 @@ package android.util; -import junit.framework.TestCase; - import java.io.IOException; import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; +import junit.framework.TestCase; public final class JsonWriterTest extends TestCase { @@ -119,7 +120,7 @@ public final class JsonWriterTest extends TestCase { JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.beginObject(); jsonWriter.name("a"); - jsonWriter.value(null); + jsonWriter.value((String) null); jsonWriter.endObject(); assertEquals("{\"a\":null}", stringWriter.toString()); } @@ -145,6 +146,27 @@ public final class JsonWriterTest extends TestCase { } } + public void testNonFiniteBoxedDoubles() throws IOException { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.beginArray(); + try { + jsonWriter.value(new Double(Double.NaN)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + jsonWriter.value(new Double(Double.NEGATIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + try { + jsonWriter.value(new Double(Double.POSITIVE_INFINITY)); + fail(); + } catch (IllegalArgumentException expected) { + } + } + public void testDoubles() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); @@ -189,6 +211,22 @@ public final class JsonWriterTest extends TestCase { + "9223372036854775807]", stringWriter.toString()); } + public void testNumbers() throws IOException { + StringWriter stringWriter = new StringWriter(); + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.beginArray(); + jsonWriter.value(new BigInteger("0")); + jsonWriter.value(new BigInteger("9223372036854775808")); + jsonWriter.value(new BigInteger("-9223372036854775809")); + jsonWriter.value(new BigDecimal("3.141592653589793238462643383")); + jsonWriter.endArray(); + jsonWriter.close(); + assertEquals("[0," + + "9223372036854775808," + + "-9223372036854775809," + + "3.141592653589793238462643383]", stringWriter.toString()); + } + public void testBooleans() throws IOException { StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter);