You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2018/05/18 20:24:54 UTC

commons-csv git commit: [CSV-224] Some Multi Iterator Parsing Peek Sequences Incorrectly Consume Elements.

Repository: commons-csv
Updated Branches:
  refs/heads/master 33f662b21 -> f368f64fa


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

Project: http://git-wip-us.apache.org/repos/asf/commons-csv/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-csv/commit/f368f64f
Tree: http://git-wip-us.apache.org/repos/asf/commons-csv/tree/f368f64f
Diff: http://git-wip-us.apache.org/repos/asf/commons-csv/diff/f368f64f

Branch: refs/heads/master
Commit: f368f64fa7f9acdcc01084f676e8b9c2b86f946e
Parents: 33f662b
Author: David Warshaw <da...@gmail.com>
Authored: Fri May 18 14:24:50 2018 -0600
Committer: Gary Gregory <ga...@gmail.com>
Committed: Fri May 18 14:24:50 2018 -0600

----------------------------------------------------------------------
 src/changes/changes.xml                         |  1 +
 .../java/org/apache/commons/csv/CSVParser.java  | 93 +++++++++++---------
 .../org/apache/commons/csv/CSVParserTest.java   | 56 ++++++++++++
 3 files changed, 106 insertions(+), 44 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-csv/blob/f368f64f/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8142c21..d94ccdd 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -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>

http://git-wip-us.apache.org/repos/asf/commons-csv/blob/f368f64f/src/main/java/org/apache/commons/csv/CSVParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java b/src/main/java/org/apache/commons/csv/CSVParser.java
index 2a902cd..7e9d7d4 100644
--- a/src/main/java/org/apache/commons/csv/CSVParser.java
+++ b/src/main/java/org/apache/commons/csv/CSVParser.java
@@ -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);
-                }
+        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 boolean hasNext() {
+            if (CSVParser.this.isClosed()) {
+                return false;
             }
-
-            @Override
-            public CSVRecord next() {
-                if (CSVParser.this.isClosed()) {
-                    throw new NoSuchElementException("CSVParser has been closed");
-                }
-                CSVRecord next = this.current;
-                this.current = null;
-
+            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) {
-                    // hasNext() wasn't called before
-                    next = this.getNextRecord();
-                    if (next == null) {
-                        throw new NoSuchElementException("No more CSV records available");
-                    }
+                    throw new NoSuchElementException("No more CSV records available");
                 }
-
-                return next;
             }
-
-            @Override
-            public void remove() {
-                throw new UnsupportedOperationException();
-            }
-        };
-    }
+  
+            return next;
+        }
+  
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    };
 
     /**
      * Parses the next record from the current point in the stream.

http://git-wip-us.apache.org/repos/asf/commons-csv/blob/f368f64f/src/test/java/org/apache/commons/csv/CSVParserTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/csv/CSVParserTest.java b/src/test/java/org/apache/commons/csv/CSVParserTest.java
index 07e06bc..1e1d7a6 100644
--- a/src/test/java/org/apache/commons/csv/CSVParserTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVParserTest.java
@@ -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))) {