[CSV-224] Some Multi Iterator Parsing Peek Sequences Incorrectly Consume

Elements.
This commit is contained in:
David Warshaw 2018-05-18 14:24:50 -06:00 committed by Gary Gregory
parent 33f662b219
commit f368f64fa7
3 changed files with 110 additions and 48 deletions

View File

@ -45,6 +45,7 @@
<action issue="CSV-220" type="add" dev="ggregory" due-to="Gary Gregory">Add API org.apache.commons.csv.CSVFormat.withSystemRecordSeparator().</action>
<action issue="CSV-223" type="fix" dev="ggregory" due-to="Samuel Martin">Inconsistency between Javadoc of CSVFormat DEFAULT EXCEL.</action>
<action issue="CSV-209" type="fix" dev="ggregory" due-to="Gary Gregory">Create CSVFormat.ORACLE preset.</action>
<action issue="CSV-224" type="fix" dev="ggregory" due-to="David Warshaw">Some Multi Iterator Parsing Peek Sequences Incorrectly Consume Elements.</action>
</release>
<release version="1.5" date="2017-09-03" description="Feature and bug fix release">
<action issue="CSV-203" type="fix" dev="ggregory" due-to="Richard Wheeldon, Kai Paroth">withNullString value is printed without quotes when QuoteMode.ALL is specified; add QuoteMode.ALL_NON_NULL. PR #17.</action>

View File

@ -286,6 +286,8 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
private final Lexer lexer;
private final CSVRecordIterator csvRecordIterator;
/** A record buffer for getRecord(). Grows as necessary and is reused. */
private final List<String> recordList = new ArrayList<>();
@ -353,6 +355,7 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
this.format = format;
this.lexer = new Lexer(format, new ExtendedBufferedReader(reader));
this.csvRecordIterator = new CSVRecordIterator();
this.headerMap = this.initializeHeader();
this.characterOffset = characterOffset;
this.recordNumber = recordNumber - 1;
@ -519,55 +522,57 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
*/
@Override
public Iterator<CSVRecord> iterator() {
return new Iterator<CSVRecord>() {
private CSVRecord current;
private CSVRecord getNextRecord() {
try {
return CSVParser.this.nextRecord();
} catch (final IOException e) {
throw new IllegalStateException(
e.getClass().getSimpleName() + " reading next record: " + e.toString(), e);
}
}
@Override
public boolean hasNext() {
if (CSVParser.this.isClosed()) {
return false;
}
if (this.current == null) {
this.current = this.getNextRecord();
}
return this.current != null;
}
@Override
public CSVRecord next() {
if (CSVParser.this.isClosed()) {
throw new NoSuchElementException("CSVParser has been closed");
}
CSVRecord next = this.current;
this.current = null;
if (next == null) {
// hasNext() wasn't called before
next = this.getNextRecord();
if (next == null) {
throw new NoSuchElementException("No more CSV records available");
}
}
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
return csvRecordIterator;
}
class CSVRecordIterator implements Iterator<CSVRecord> {
private CSVRecord current;
private CSVRecord getNextRecord() {
try {
return CSVParser.this.nextRecord();
} catch (final IOException e) {
throw new IllegalStateException(
e.getClass().getSimpleName() + " reading next record: " + e.toString(), e);
}
}
@Override
public boolean hasNext() {
if (CSVParser.this.isClosed()) {
return false;
}
if (this.current == null) {
this.current = this.getNextRecord();
}
return this.current != null;
}
@Override
public CSVRecord next() {
if (CSVParser.this.isClosed()) {
throw new NoSuchElementException("CSVParser has been closed");
}
CSVRecord next = this.current;
this.current = null;
if (next == null) {
// hasNext() wasn't called before
next = this.getNextRecord();
if (next == null) {
throw new NoSuchElementException("No more CSV records available");
}
}
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
/**
* Parses the next record from the current point in the stream.

View File

@ -989,6 +989,62 @@ public class CSVParserTest {
Assert.assertEquals(3, record.size());
}
@Test
public void testIteratorSequenceBreaking() throws IOException {
final String fiveRows = "1\n2\n3\n4\n5\n";
// Iterator hasNext() shouldn't break sequence
CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
int recordNumber = 0;
Iterator<CSVRecord> iter = parser.iterator();
recordNumber = 0;
while (iter.hasNext()) {
CSVRecord record = iter.next();
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
break;
}
}
iter.hasNext();
while (iter.hasNext()) {
CSVRecord record = iter.next();
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
// Consecutive enhanced for loops shouldn't break sequence
parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
recordNumber = 0;
for (CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
break;
}
}
for (CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
// Consecutive enhanced for loops with hasNext() peeking shouldn't break sequence
parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows));
recordNumber = 0;
for (CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
if (recordNumber >= 2) {
break;
}
}
parser.iterator().hasNext();
for (CSVRecord record : parser) {
recordNumber++;
assertEquals(String.valueOf(recordNumber), record.get(0));
}
}
private void validateLineNumbers(final String lineSeparator) throws IOException {
try (final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c",
CSVFormat.DEFAULT.withRecordSeparator(lineSeparator))) {