Remove EOFException special casing of JsonStreamParser.next() (#2281)

* Remove EOFException special casing of JsonStreamParser.next()

The previous behavior violated the Iterator contract where for
`JsonStreamParser("[")` a call to `hasNext()` would return true,
but `next()` would throw a NoSuchElementException.

* Fix incorrect documented thrown exception type for JsonStreamParser
This commit is contained in:
Marcono1234 2022-12-14 17:33:33 +01:00 committed by GitHub
parent 6c3cf22435
commit f63a1b85ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 22 deletions

View File

@ -15,18 +15,16 @@
*/ */
package com.google.gson; package com.google.gson;
import java.io.EOFException; import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.util.Iterator; import java.util.Iterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.MalformedJsonException;
/** /**
* A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader * A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
* asynchronously. The JSON data is parsed in lenient mode, see also * asynchronously. The JSON data is parsed in lenient mode, see also
@ -61,7 +59,7 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
public JsonStreamParser(String json) { public JsonStreamParser(String json) {
this(new StringReader(json)); this(new StringReader(json));
} }
/** /**
* @param reader The data stream containing JSON elements concatenated to each other. * @param reader The data stream containing JSON elements concatenated to each other.
* @since 1.4 * @since 1.4
@ -71,13 +69,13 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
parser.setLenient(true); parser.setLenient(true);
lock = new Object(); lock = new Object();
} }
/** /**
* Returns the next available {@link JsonElement} on the reader. Throws a * Returns the next available {@link JsonElement} on the reader. Throws a
* {@link NoSuchElementException} if no element is available. * {@link NoSuchElementException} if no element is available.
* *
* @return the next available {@code JsonElement} on the reader. * @return the next available {@code JsonElement} on the reader.
* @throws JsonSyntaxException if the incoming stream is malformed JSON. * @throws JsonParseException if the incoming stream is malformed JSON.
* @throws NoSuchElementException if no {@code JsonElement} is available. * @throws NoSuchElementException if no {@code JsonElement} is available.
* @since 1.4 * @since 1.4
*/ */
@ -86,22 +84,20 @@ public final class JsonStreamParser implements Iterator<JsonElement> {
if (!hasNext()) { if (!hasNext()) {
throw new NoSuchElementException(); throw new NoSuchElementException();
} }
try { try {
return Streams.parse(parser); return Streams.parse(parser);
} catch (StackOverflowError e) { } catch (StackOverflowError e) {
throw new JsonParseException("Failed parsing JSON source to Json", e); throw new JsonParseException("Failed parsing JSON source to Json", e);
} catch (OutOfMemoryError e) { } catch (OutOfMemoryError e) {
throw new JsonParseException("Failed parsing JSON source to Json", e); throw new JsonParseException("Failed parsing JSON source to Json", e);
} catch (JsonParseException e) {
throw e.getCause() instanceof EOFException ? new NoSuchElementException() : e;
} }
} }
/** /**
* Returns true if a {@link JsonElement} is available on the input for consumption * Returns true if a {@link JsonElement} is available on the input for consumption
* @return true if a {@link JsonElement} is available on the input, false otherwise * @return true if a {@link JsonElement} is available on the input, false otherwise
* @throws JsonSyntaxException if the incoming stream is malformed JSON. * @throws JsonParseException if the incoming stream is malformed JSON.
* @since 1.4 * @since 1.4
*/ */
@Override @Override

View File

@ -15,24 +15,30 @@
*/ */
package com.google.gson; package com.google.gson;
import junit.framework.TestCase; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.EOFException;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.junit.Before;
import org.junit.Test;
/** /**
* Unit tests for {@link JsonStreamParser} * Unit tests for {@link JsonStreamParser}
* *
* @author Inderjeet Singh * @author Inderjeet Singh
*/ */
public class JsonStreamParserTest extends TestCase { public class JsonStreamParserTest {
private JsonStreamParser parser; private JsonStreamParser parser;
@Override @Before
protected void setUp() throws Exception { public void setUp() throws Exception {
super.setUp();
parser = new JsonStreamParser("'one' 'two'"); parser = new JsonStreamParser("'one' 'two'");
} }
@Test
public void testParseTwoStrings() { public void testParseTwoStrings() {
String actualOne = parser.next().getAsString(); String actualOne = parser.next().getAsString();
assertEquals("one", actualOne); assertEquals("one", actualOne);
@ -40,6 +46,7 @@ public class JsonStreamParserTest extends TestCase {
assertEquals("two", actualTwo); assertEquals("two", actualTwo);
} }
@Test
public void testIterator() { public void testIterator() {
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertEquals("one", parser.next().getAsString()); assertEquals("one", parser.next().getAsString());
@ -48,20 +55,22 @@ public class JsonStreamParserTest extends TestCase {
assertFalse(parser.hasNext()); assertFalse(parser.hasNext());
} }
@Test
public void testNoSideEffectForHasNext() throws Exception { public void testNoSideEffectForHasNext() throws Exception {
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertEquals("one", parser.next().getAsString()); assertEquals("one", parser.next().getAsString());
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertTrue(parser.hasNext()); assertTrue(parser.hasNext());
assertEquals("two", parser.next().getAsString()); assertEquals("two", parser.next().getAsString());
assertFalse(parser.hasNext()); assertFalse(parser.hasNext());
assertFalse(parser.hasNext()); assertFalse(parser.hasNext());
} }
@Test
public void testCallingNextBeyondAvailableInput() { public void testCallingNextBeyondAvailableInput() {
parser.next(); parser.next();
parser.next(); parser.next();
@ -71,4 +80,51 @@ public class JsonStreamParserTest extends TestCase {
} catch (NoSuchElementException expected) { } catch (NoSuchElementException expected) {
} }
} }
@Test
public void testEmptyInput() {
JsonStreamParser parser = new JsonStreamParser("");
try {
parser.next();
fail();
} catch (JsonIOException e) {
assertTrue(e.getCause() instanceof EOFException);
}
parser = new JsonStreamParser("");
try {
parser.hasNext();
fail();
} catch (JsonIOException e) {
assertTrue(e.getCause() instanceof EOFException);
}
}
@Test
public void testIncompleteInput() {
JsonStreamParser parser = new JsonStreamParser("[");
assertTrue(parser.hasNext());
try {
parser.next();
fail();
} catch (JsonSyntaxException e) {
}
}
@Test
public void testMalformedInput() {
JsonStreamParser parser = new JsonStreamParser(":");
try {
parser.hasNext();
fail();
} catch (JsonSyntaxException e) {
}
parser = new JsonStreamParser(":");
try {
parser.next();
fail();
} catch (JsonSyntaxException e) {
}
}
} }