This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-csv.git


The following commit(s) were added to refs/heads/master by this push:
     new 289ffa16 Validates that a CSVFormat rejects empty header names
289ffa16 is described below

commit 289ffa16275c518722c6cda913bfca8a6e1a1411
Author: Gary Gregory <garydgreg...@gmail.com>
AuthorDate: Sat Oct 22 08:38:52 2022 -0400

    Validates that a CSVFormat rejects empty header names
---
 .../java/org/apache/commons/csv/CSVFormat.java     | 35 ++++++++++++----------
 .../java/org/apache/commons/csv/CSVFormatTest.java | 15 ++++++++++
 2 files changed, 35 insertions(+), 15 deletions(-)

diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java 
b/src/main/java/org/apache/commons/csv/CSVFormat.java
index e611c63a..44fecfc5 100644
--- a/src/main/java/org/apache/commons/csv/CSVFormat.java
+++ b/src/main/java/org/apache/commons/csv/CSVFormat.java
@@ -253,7 +253,7 @@ public final class CSVFormat implements Serializable {
             this.recordSeparator = csvFormat.recordSeparator;
             this.nullString = csvFormat.nullString;
             this.headerComments = csvFormat.headerComments;
-            this.headers = csvFormat.header;
+            this.headers = csvFormat.headers;
             this.skipHeaderRecord = csvFormat.skipHeaderRecord;
             this.ignoreHeaderCase = csvFormat.ignoreHeaderCase;
             this.trailingDelimiter = csvFormat.trailingDelimiter;
@@ -1311,7 +1311,7 @@ public final class CSVFormat implements Serializable {
 
     private final Character escapeCharacter; // null if escaping is disabled
 
-    private final String[] header; // array of header column names
+    private final String[] headers; // array of header column names
 
     private final String[] headerComments; // array of header comment lines
 
@@ -1349,7 +1349,7 @@ public final class CSVFormat implements Serializable {
         this.recordSeparator = builder.recordSeparator;
         this.nullString = builder.nullString;
         this.headerComments = builder.headerComments;
-        this.header = builder.headers;
+        this.headers = builder.headers;
         this.skipHeaderRecord = builder.skipHeaderRecord;
         this.ignoreHeaderCase = builder.ignoreHeaderCase;
         this.trailingDelimiter = builder.trailingDelimiter;
@@ -1399,7 +1399,7 @@ public final class CSVFormat implements Serializable {
         this.recordSeparator = recordSeparator;
         this.nullString = nullString;
         this.headerComments = toStringArray(headerComments);
-        this.header = clone(header);
+        this.headers = clone(header);
         this.skipHeaderRecord = skipHeaderRecord;
         this.ignoreHeaderCase = ignoreHeaderCase;
         this.trailingDelimiter = trailingDelimiter;
@@ -1455,7 +1455,7 @@ public final class CSVFormat implements Serializable {
         final CSVFormat other = (CSVFormat) obj;
         return duplicateHeaderMode == other.duplicateHeaderMode && 
allowMissingColumnNames == other.allowMissingColumnNames &&
                 autoFlush == other.autoFlush && Objects.equals(commentMarker, 
other.commentMarker) && Objects.equals(delimiter, other.delimiter) &&
-                Objects.equals(escapeCharacter, other.escapeCharacter) && 
Arrays.equals(header, other.header) &&
+                Objects.equals(escapeCharacter, other.escapeCharacter) && 
Arrays.equals(headers, other.headers) &&
                 Arrays.equals(headerComments, other.headerComments) && 
ignoreEmptyLines == other.ignoreEmptyLines &&
                 ignoreHeaderCase == other.ignoreHeaderCase && 
ignoreSurroundingSpaces == other.ignoreSurroundingSpaces &&
                 Objects.equals(nullString, other.nullString) && 
Objects.equals(quoteCharacter, other.quoteCharacter) && quoteMode == 
other.quoteMode &&
@@ -1568,7 +1568,7 @@ public final class CSVFormat implements Serializable {
      * @return a copy of the header array; {@code null} if disabled, the empty 
array if to be read from the file
      */
     public String[] getHeader() {
-        return header != null ? header.clone() : null;
+        return headers != null ? headers.clone() : null;
     }
 
     /**
@@ -1681,7 +1681,7 @@ public final class CSVFormat implements Serializable {
     public int hashCode() {
         final int prime = 31;
         int result = 1;
-        result = prime * result + Arrays.hashCode(header);
+        result = prime * result + Arrays.hashCode(headers);
         result = prime * result + Arrays.hashCode(headerComments);
         return prime * result + Objects.hash(duplicateHeaderMode, 
allowMissingColumnNames, autoFlush, commentMarker, delimiter, escapeCharacter,
                 ignoreEmptyLines, ignoreHeaderCase, ignoreSurroundingSpaces, 
nullString, quoteCharacter, quoteMode, quotedNullString, recordSeparator,
@@ -2243,9 +2243,9 @@ public final class CSVFormat implements Serializable {
             sb.append(' ');
             
sb.append("HeaderComments:").append(Arrays.toString(headerComments));
         }
-        if (header != null) {
+        if (headers != null) {
             sb.append(' ');
-            sb.append("Header:").append(Arrays.toString(header));
+            sb.append("Header:").append(Arrays.toString(headers));
         }
         return sb.toString();
     }
@@ -2284,12 +2284,17 @@ public final class CSVFormat implements Serializable {
             throw new IllegalArgumentException("No quotes mode set but no 
escape character is set");
         }
 
-        // validate header
-        if (header != null && duplicateHeaderMode != 
DuplicateHeaderMode.ALLOW_ALL) {
-            final Set<String> dupCheck = new HashSet<>();
-            for (final String hdr : header) {
-                if (!dupCheck.add(hdr)) {
-                    throw new IllegalArgumentException("The header contains a 
duplicate entry: '" + hdr + "' in " + Arrays.toString(header));
+        // Validate headers
+        if (headers != null && duplicateHeaderMode != 
DuplicateHeaderMode.ALLOW_ALL) {
+            final Set<String> dupCheckSet = new HashSet<>(headers.length);
+            final boolean rejectEmpty = duplicateHeaderMode != 
DuplicateHeaderMode.ALLOW_EMPTY;
+            for (final String header : headers) {
+                final boolean empty = header == null || header.isEmpty();
+                if (rejectEmpty && empty) {
+                    throw new IllegalArgumentException("Header is empty");
+                }
+                if (!empty && !dupCheckSet.add(header)) {
+                    throw new IllegalArgumentException(String.format("Header 
'%s' is a duplicate in %s", header, Arrays.toString(headers)));
                 }
             }
         }
diff --git a/src/test/java/org/apache/commons/csv/CSVFormatTest.java 
b/src/test/java/org/apache/commons/csv/CSVFormatTest.java
index 31f387f1..4fa96196 100644
--- a/src/test/java/org/apache/commons/csv/CSVFormatTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVFormatTest.java
@@ -151,6 +151,21 @@ public class CSVFormatTest {
         CSVFormat.DEFAULT.withAllowDuplicateHeaderNames(true).withHeader("A", 
"A");
     }
 
+    @Test
+    public void testDuplicateHeaderElementsTrueContainsEmpty1() {
+        
CSVFormat.DEFAULT.builder().setAllowDuplicateHeaderNames(false).setHeader("A", 
"", "B", "").build();
+    }
+
+    @Test
+    public void testDuplicateHeaderElementsTrueContainsEmpty2() {
+        
CSVFormat.DEFAULT.builder().setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_EMPTY).setHeader("A",
 "", "B", "").build();
+    }
+
+    @Test
+    public void testDuplicateHeaderElementsTrueContainsEmpty3() {
+        
CSVFormat.DEFAULT.builder().setAllowDuplicateHeaderNames(false).setAllowMissingColumnNames(true).setHeader("A",
 "", "B", "").build();
+    }
+
     @Test
     public void testEquals() {
         final CSVFormat right = CSVFormat.DEFAULT;

Reply via email to