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 5e90c013..a936ddf9 100644 --- a/gson/src/main/java/com/google/gson/stream/JsonReader.java +++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java @@ -204,6 +204,14 @@ public final class JsonReader implements Closeable { private int pos = 0; private int limit = 0; + /* + * Track the number of newlines and columns preceding the current buffer. To + * compute the line and column of a position in the buffer, compute the line + * and column in the buffer and add the preceding values. + */ + private int bufferStartLine; + private int bufferStartColumn; + private final List stack = new ArrayList(); { push(JsonScope.EMPTY_DOCUMENT); @@ -817,6 +825,16 @@ public final class JsonReader implements Closeable { * false. */ private boolean fillBuffer(int minimum) throws IOException { + // Before clobbering the old characters, update where buffer starts + for (int i = 0; i < pos; i++) { + if (buffer[i] == '\n') { + bufferStartLine++; + bufferStartColumn = 0; + } else { + bufferStartColumn++; + } + } + if (limit != pos) { limit -= pos; System.arraycopy(buffer, pos, buffer, 0, limit); @@ -835,6 +853,28 @@ public final class JsonReader implements Closeable { return false; } + private int getLineNumber() { + int result = bufferStartLine; + for (int i = 0; i < pos; i++) { + if (buffer[i] == '\n') { + result++; + } + } + return result + 1; // the first line is '1' + } + + private int getColumnNumber() { + int result = bufferStartColumn; + for (int i = 0; i < pos; i++) { + if (buffer[i] == '\n') { + result = 0; + } else { + result++; + } + } + return result + 1; // the first column is '1' + } + private int nextNonWhitespace() throws IOException { while (pos < limit || fillBuffer(1)) { int c = buffer[pos++]; @@ -1110,7 +1150,7 @@ public final class JsonReader implements Closeable { * with this reader's content. */ private IOException syntaxError(String message) throws IOException { - throw new MalformedJsonException(message + " near " + getSnippet()); + throw new MalformedJsonException(message + " @" + getLineNumber() + ":" + getColumnNumber()); } private CharSequence getSnippet() { 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 49c1b5c2..d7e6a0b9 100644 --- a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java +++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java @@ -18,6 +18,7 @@ package com.google.gson.stream; import java.io.IOException; import java.io.StringReader; +import java.util.Arrays; import junit.framework.TestCase; public final class JsonReaderTest extends TestCase { @@ -763,4 +764,35 @@ public final class JsonReaderTest extends TestCase { } catch (IOException expected) { } } + + public void testFailWithPosition() throws IOException { + JsonReader reader = new JsonReader(new StringReader("[\n\n\n\n\n0,}]")); + reader.beginArray(); + reader.nextInt(); + try { + reader.peek(); + fail(); + } catch (IOException expected) { + assertEquals("Expected literal value @6:3", expected.getMessage()); + } + } + + public void testFailWithPositionGreaterThanBufferSize() throws IOException { + String spaces = repeat(' ', 8192); + JsonReader reader = new JsonReader(new StringReader("[\n\n" + spaces + "\n\n\n0,}]")); + reader.beginArray(); + reader.nextInt(); + try { + reader.peek(); + fail(); + } catch (IOException expected) { + assertEquals("Expected literal value @6:3", expected.getMessage()); + } + } + + private String repeat(char c, int count) { + char[] array = new char[count]; + Arrays.fill(array, c); + return new String(array); + } }