Repository: jtablesaw/tablesaw
Pull request #1032 (2021-10-31 16:04:53)

Provides better error message on column type detection index out of b…
…ounds

Thanks for contributing.

- [x] Tick to sign-off your agreement to the [Developer Certificate of Origin (DCO) 1.1](https://developercertificate.org)

## Description

Added exception class to wrap IndexOutOfBoundsException and provide info about the row causing the exception.


## Testing

Did you add a unit test? Yes



Patch:
diff --git a/core/src/main/java/tech/tablesaw/io/AddCellToColumnException.java b/core/src/main/java/tech/tablesaw/io/AddCellToColumnException.java
index aa5fe0da..2d801977 100644
--- a/core/src/main/java/tech/tablesaw/io/AddCellToColumnException.java
+++ b/core/src/main/java/tech/tablesaw/io/AddCellToColumnException.java
@@ -45,7 +45,7 @@ public class AddCellToColumnException extends RuntimeException {
   /**
    * Creates a new instance of this Exception
    *
-   * @param e The Exceeption that caused adding to fail
+   * @param e The Exception that caused adding to fail
    * @param columnIndex The index of the column that threw the Exception
    * @param rowNumber The number of the row that caused the Exception to be thrown
    * @param columnNames The column names stored as an array
diff --git a/core/src/main/java/tech/tablesaw/io/ColumnIndexOutOfBoundsException.java b/core/src/main/java/tech/tablesaw/io/ColumnIndexOutOfBoundsException.java
new file mode 100644
index 00000000..793edfa9
--- /dev/null
+++ b/core/src/main/java/tech/tablesaw/io/ColumnIndexOutOfBoundsException.java
@@ -0,0 +1,56 @@
+package tech.tablesaw.io;
+
+import java.io.PrintStream;
+import java.util.Arrays;
+
+/**
+ * This Exception wraps another Exception thrown while adding a cell to a column.
+ *
+ * <p>The methods of this exception allow the causing Exception, row number, column index,
+ * columnNames and line to be retrieved.
+ *
+ * <p>The dumpRow method allows the row in question to be printed to a a PrintStream such as
+ * System.out
+ */
+public class ColumnIndexOutOfBoundsException extends RuntimeException {
+
+  private static final long serialVersionUID = 1L;
+
+  /** The number of the row that caused the exception to be thrown */
+  private final long rowNumber;
+
+  private final String[] line;
+
+  public ColumnIndexOutOfBoundsException(
+      IndexOutOfBoundsException e, long rowNumber, String[] line) {
+    super(
+        "An IndexOutOfBoundsException occurred while detecting column types from row "
+            + rowNumber
+            + " with values: "
+            + Arrays.toString(line)
+            + ": "
+            + e.getMessage(),
+        e);
+    this.rowNumber = rowNumber;
+    this.line = line;
+  }
+
+  /** Returns the number of the row that caused the Exception to be thrown */
+  public long getRowNumber() {
+    return rowNumber;
+  }
+
+  /** Returns the array of values in the row that caused the Exception as a comma-separated list */
+  public String[] getLine() {
+    return line;
+  }
+
+  /**
+   * Dumps to a PrintStream the information relative to the row that caused the problem
+   *
+   * @param out The PrintStream to output to
+   */
+  public void dumpRow(PrintStream out) {
+    out.println(Arrays.toString(line));
+  }
+}
diff --git a/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java b/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
index 5eef7383..43eacd04 100644
--- a/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
+++ b/core/src/main/java/tech/tablesaw/io/ColumnTypeDetector.java
@@ -3,10 +3,7 @@ package tech.tablesaw.io;
 import static tech.tablesaw.api.ColumnType.STRING;
 import static tech.tablesaw.api.ColumnType.TEXT;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import tech.tablesaw.api.ColumnType;
 import tech.tablesaw.columns.AbstractColumnParser;
@@ -71,28 +68,34 @@ public class ColumnTypeDetector {
     int rowCount = 0; // make sure we don't go over maxRows
 
     int nextRow = 0;
+    String[] nextLine = new String[0];
     while (rows.hasNext()) {
-      String[] nextLine = rows.next();
-      // initialize the arrays to hold the strings. we don't know how many we need until we read the
-      // first row
-      if (rowCount == 0) {
-        for (int i = 0; i < nextLine.length; i++) {
-          columnData.add(new ArrayList<>());
-        }
-      }
-      int columnNumber = 0;
-      if (rowCount == nextRow) {
-        for (String field : nextLine) {
-          columnData.get(columnNumber).add(field);
-          columnNumber++;
+      try {
+        nextLine = rows.next();
+        // initialize the arrays to hold the strings. we don't know how many we need until we read
+        // the
+        // first row
+        if (rowCount == 0) {
+          for (int i = 0; i < nextLine.length; i++) {
+            columnData.add(new ArrayList<>());
+          }
         }
-        if (useSampling) {
-          nextRow = nextRow(nextRow);
-        } else {
-          nextRow = nextRowWithoutSampling(nextRow);
+        int columnNumber = 0;
+        if (rowCount == nextRow) {
+          for (String field : nextLine) {
+            columnData.get(columnNumber).add(field);
+            columnNumber++;
+          }
+          if (useSampling) {
+            nextRow = nextRow(nextRow);
+          } else {
+            nextRow = nextRowWithoutSampling(nextRow);
+          }
         }
+        rowCount++;
+      } catch (IndexOutOfBoundsException e) {
+        throw new ColumnIndexOutOfBoundsException(e, nextRow, nextLine);
       }
-      rowCount++;
     }
 
     // now detect


Test patch:
diff --git a/core/src/test/java/tech/tablesaw/io/ColumnTypeDetectorTest.java b/core/src/test/java/tech/tablesaw/io/ColumnTypeDetectorTest.java
index fabff033..2663d858 100644
--- a/core/src/test/java/tech/tablesaw/io/ColumnTypeDetectorTest.java
+++ b/core/src/test/java/tech/tablesaw/io/ColumnTypeDetectorTest.java
@@ -14,15 +14,19 @@ import static tech.tablesaw.api.ColumnType.STRING;
 import static tech.tablesaw.api.ColumnType.TEXT;
 
 import com.google.common.collect.Lists;
+import java.io.StringReader;
 import java.util.ArrayList;
 import org.junit.jupiter.api.Test;
 import tech.tablesaw.api.ColumnType;
+import tech.tablesaw.api.Table;
 import tech.tablesaw.columns.dates.DateColumnType;
 import tech.tablesaw.columns.strings.StringColumnType;
 import tech.tablesaw.columns.strings.TextColumnType;
 
 class ColumnTypeDetectorTest {
 
+  private static final String LINE_END = System.lineSeparator();
+
   @Test
   void detectColumnTypes() {
 
@@ -51,4 +55,20 @@ class ColumnTypeDetectorTest {
     assertEquals(DateColumnType.instance(), types[1]);
     assertEquals(StringColumnType.instance(), types[2]);
   }
+
+  /**
+   * Test to ensure a useful error message is thrown when type detection runs into a row with an
+   * extra comma at the end
+   */
+  @Test
+  void errorMsg() {
+    String df =
+        "subject, time, age, weight, height"
+            + LINE_END
+            + "John Smith,    1,  33,     90,   1.87"
+            + LINE_END
+            + "Mary Smith,    1,  NA,     NA,   1.54, ";
+    StringReader reader = new StringReader(df);
+    assertThrows(ColumnIndexOutOfBoundsException.class, () -> Table.read().csv(reader));
+  }
 }