-
Notifications
You must be signed in to change notification settings - Fork 304
Fill the lookahead buffer on short reads from a chunked source #625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1709,6 +1709,47 @@ void testParseWithDelimiterStringWithEscape() throws IOException { | |
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testParseWithDelimiterStringFromChunkedReader() throws IOException { | ||
| // A reader that hands out one character at a time and never reports itself ready, like a socket or pipe. | ||
| final Reader chunked = new Reader() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is the same as the behavior of the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, they were the same thing. Pulled it out into a package-private |
||
|
|
||
| private final String content = "a[|]b\r\nc![!|!]d[|]e"; | ||
| private int index; | ||
|
|
||
| @Override | ||
| public void close() { | ||
| // nothing to close | ||
| } | ||
|
|
||
| @Override | ||
| public boolean ready() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public int read(final char[] buf, final int offset, final int length) { | ||
| if (index >= content.length()) { | ||
| return -1; | ||
| } | ||
| if (length <= 0) { | ||
| return 0; | ||
| } | ||
| buf[offset] = content.charAt(index++); | ||
| return 1; | ||
| } | ||
| }; | ||
| final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').get(); | ||
| try (CSVParser csvParser = csvFormat.parse(chunked)) { | ||
| CSVRecord csvRecord = csvParser.nextRecord(); | ||
| assertEquals("a", csvRecord.get(0)); | ||
| assertEquals("b", csvRecord.get(1)); | ||
| csvRecord = csvParser.nextRecord(); | ||
| assertEquals("c[|]d", csvRecord.get(0)); | ||
| assertEquals("e", csvRecord.get(1)); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testParseWithDelimiterStringWithQuote() throws IOException { | ||
| final String source = "'a[|]b[|]c'[|]xyz\r\nabc[abc][|]xyz"; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -240,4 +240,72 @@ void testReadLookahead2() throws Exception { | |
| assertEquals('d', br.getLastChar()); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testReadAndPeekArrayFromChunkedReader() throws Exception { | ||
| try (ExtendedBufferedReader br = new ExtendedBufferedReader(new ChunkedReader("abcdef"))) { | ||
| final char[] peeked = new char[3]; | ||
| assertEquals(3, br.peek(peeked)); | ||
| assertArrayEquals(new char[] { 'a', 'b', 'c' }, peeked); | ||
| final char[] read = new char[3]; | ||
| assertEquals(3, br.read(read, 0, 3)); | ||
| assertArrayEquals(new char[] { 'a', 'b', 'c' }, read); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testReadArrayPastEndOfChunkedReader() throws Exception { | ||
| try (ExtendedBufferedReader br = new ExtendedBufferedReader(new ChunkedReader("ab"))) { | ||
| final char[] read = new char[4]; | ||
| assertEquals(2, br.read(read, 0, 4)); | ||
| assertEquals('a', read[0]); | ||
| assertEquals('b', read[1]); | ||
| assertEquals(EOF, br.read(read, 0, 4)); | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| void testPeekArrayPastEndOfChunkedReader() throws Exception { | ||
| try (ExtendedBufferedReader br = new ExtendedBufferedReader(new ChunkedReader("ab"))) { | ||
| final char[] peeked = new char[4]; | ||
| assertEquals(2, br.peek(peeked)); | ||
| assertEquals('a', peeked[0]); | ||
| assertEquals('b', peeked[1]); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * A reader that returns one character per call and never reports itself ready, like a socket or pipe that delivers data in chunks. | ||
| */ | ||
| private static final class ChunkedReader extends java.io.Reader { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be simpler to extend
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. It's now a small |
||
|
|
||
| private final String content; | ||
| private int index; | ||
|
|
||
| ChunkedReader(final String content) { | ||
| this.content = content; | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| // nothing to close | ||
| } | ||
|
|
||
| @Override | ||
| public boolean ready() { | ||
| return false; | ||
| } | ||
|
|
||
| @Override | ||
| public int read(final char[] buf, final int offset, final int length) { | ||
| if (index >= content.length()) { | ||
| return EOF; | ||
| } | ||
| if (length <= 0) { | ||
| return 0; | ||
| } | ||
| buf[offset] = content.charAt(index++); | ||
| return 1; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there still a risk that even filling to the end of the buffer breaks up multi-character sequences?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because the callers size the array to the exact sequence they're matching. Lexer allocates
delimiterBufasdelimiter.length - 1andescapeDelimiterBufas2 * delimiter.length - 1, so once the array is full it holds the whole tail of the delimiter (or escaped delimiter), never a piece of it. Same forCSVFormat.printWithEscapes, which peeksdelimiter.length - 1.The only way the loop exits short now is EOF, and then the remaining characters genuinely don't exist, so no complete sequence can be there to miss. Before the change the loop could exit short just because the source wasn't ready yet, which is the case that broke.