diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java b/src/main/java/org/apache/commons/csv/CSVFormat.java index 954f08cb..1626a6a9 100644 --- a/src/main/java/org/apache/commons/csv/CSVFormat.java +++ b/src/main/java/org/apache/commons/csv/CSVFormat.java @@ -289,7 +289,7 @@ public final class CSVFormat implements Serializable { } /** - * Sets the missing column names parser behavior, {@code true} to allow missing column names in the header line, {@code false} to cause an + * Sets the parser missing column names behavior, {@code true} to allow missing column names in the header line, {@code false} to cause an * {@link IllegalArgumentException} to be thrown. * * @param allowMissingColumnNames the missing column names behavior, {@code true} to allow missing column names in the header line, {@code false} to @@ -564,7 +564,7 @@ public final class CSVFormat implements Serializable { } /** - * Sets the case mapping behavior, {@code true} to access name/values, {@code false} to leave the mapping as is. + * Sets the parser case mapping behavior, {@code true} to access name/values, {@code false} to leave the mapping as is. * * @param ignoreHeaderCase the case mapping behavior, {@code true} to access name/values, {@code false} to leave the mapping as is. * @return This instance. @@ -1599,7 +1599,7 @@ public final class CSVFormat implements Serializable { } /** - * Gets whether header names will be accessed ignoring case. + * Gets whether header names will be accessed ignoring case when parsing input. * * @return {@code true} if header names cases are ignored, {@code false} if they are case sensitive. * @since 1.3 diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java b/src/main/java/org/apache/commons/csv/CSVParser.java index 451b7c9f..96e77a77 100644 --- a/src/main/java/org/apache/commons/csv/CSVParser.java +++ b/src/main/java/org/apache/commons/csv/CSVParser.java @@ -499,6 +499,8 @@ public final class CSVParser implements Iterable, Closeable { // build the name to index mappings if (headerRecord != null) { + // Track an occurrence of a null, empty or blank header. + boolean observedMissing = false; for (int i = 0; i < headerRecord.length; i++) { final String header = headerRecord[i]; final boolean blankHeader = CSVFormat.isBlank(header); @@ -507,7 +509,7 @@ public final class CSVParser implements Iterable, Closeable { "A header name is missing in " + Arrays.toString(headerRecord)); } - final boolean containsHeader = header != null && hdrMap.containsKey(header); + final boolean containsHeader = blankHeader ? observedMissing : hdrMap.containsKey(header); final DuplicateHeaderMode headerMode = this.format.getDuplicateHeaderMode(); final boolean duplicatesAllowed = headerMode == DuplicateHeaderMode.ALLOW_ALL; final boolean emptyDuplicatesAllowed = headerMode == DuplicateHeaderMode.ALLOW_EMPTY; @@ -518,6 +520,7 @@ public final class CSVParser implements Iterable, Closeable { "The header contains a duplicate name: \"%s\" in %s. If this is valid then use CSVFormat.Builder.setDuplicateHeaderMode().", header, Arrays.toString(headerRecord))); } + observedMissing |= blankHeader; if (header != null) { hdrMap.put(header, Integer.valueOf(i)); if (headerNames == null) { diff --git a/src/test/java/org/apache/commons/csv/CSVDuplicateHeaderTest.java b/src/test/java/org/apache/commons/csv/CSVDuplicateHeaderTest.java index 554fbb35..b8773b18 100644 --- a/src/test/java/org/apache/commons/csv/CSVDuplicateHeaderTest.java +++ b/src/test/java/org/apache/commons/csv/CSVDuplicateHeaderTest.java @@ -46,144 +46,181 @@ public class CSVDuplicateHeaderTest { */ static Stream duplicateHeaderData() { return Stream.of( - // TODO: Fix CSVParser which does not sanitise 'empty' names or - // equate null names with empty as duplications - // Any combination with a valid header - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "B"}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "B"}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "B"}, true), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "B"}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "B"}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "B"}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "B"}, true), // Any combination with a valid header including empty - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", ""}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", ""}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", ""}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", ""}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", ""}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", ""}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", ""}, true), // Any combination with a valid header including blank (1 space) - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", " "}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", " "}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", " "}, true), // Any combination with a valid header including null - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", null}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", null}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", null}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", null}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", null}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", null}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", null}, true), // Duplicate non-empty names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "A"}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "A"}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "A"}, true), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "A"}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "A"}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "A"}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "A"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "A"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "A"}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "A"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "A"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "A"}, true), // Duplicate empty names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"", ""}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"", ""}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"", ""}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"", ""}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"", ""}, true), // Duplicate blank names (1 space) - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {" ", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {" ", " "}, true), // Duplicate blank names (3 spaces) - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {" ", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {" ", " "}, true), // Duplicate null names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {null, null}, false), - // Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {null, null}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {null, null}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {null, null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {null, null}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {null, null}, true), // Duplicate blank names (1+3 spaces) - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {" ", " "}, false), - // Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {" ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {" ", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {" ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {" ", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {" ", " "}, true), // Duplicate blank names and null names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {" ", null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {" ", null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {" ", null}, false), - // Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {" ", null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {" ", null}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {" ", null}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {" ", null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {" ", null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {" ", null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {" ", null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {" ", null}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {" ", null}, true), // Duplicate non-empty and empty names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "A", "", ""}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "A", "", ""}, true), + + // Non-duplicate non-empty and duplicate empty names + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "B", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "B", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "B", "", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "B", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "B", "", ""}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "B", "", ""}, true), // Duplicate non-empty and blank names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "A", " ", " "}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "A", " ", " "}, true), // Duplicate non-empty and null names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "A", null, null}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "A", null, null}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "A", null, null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "A", null, null}, true), // Duplicate blank names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", "", ""}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", "", ""}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", "", ""}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", "", ""}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", "", ""}, true), // Duplicate null names - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", null, null}, false), - // Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", null, null}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", null, null}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", null, null}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", null, null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", null, null}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", null, null}, true), // Duplicate blank names (1+3 spaces) - Arguments.of(DuplicateHeaderMode.DISALLOW, false, new String[] {"A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, new String[] {"A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, new String[] {"A", " ", " "}, false), - // Arguments.of(DuplicateHeaderMode.DISALLOW, true, new String[] {"A", " ", " "}, false), - Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, new String[] {"A", " ", " "}, true), - Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, new String[] {"A", " ", " "}, true) + Arguments.of(DuplicateHeaderMode.DISALLOW, false, false, new String[] {"A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, false, new String[] {"A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, false, new String[] {"A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, false, new String[] {"A", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, false, new String[] {"A", " ", " "}, true), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, false, new String[] {"A", " ", " "}, true), + + // Duplicate names (case insensitive) + Arguments.of(DuplicateHeaderMode.DISALLOW, false, true , new String[] {"A", "a"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, true , new String[] {"A", "a"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, true , new String[] {"A", "a"}, true), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, true , new String[] {"A", "a"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, true , new String[] {"A", "a"}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, true , new String[] {"A", "a"}, true), + + // Duplicate non-empty (case insensitive) and empty names + Arguments.of(DuplicateHeaderMode.DISALLOW, false, true, new String[] {"A", "a", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, true, new String[] {"A", "a", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, true, new String[] {"A", "a", "", ""}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, true, new String[] {"A", "a", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, true, new String[] {"A", "a", "", ""}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, true, new String[] {"A", "a", "", ""}, true), + + // Duplicate non-empty (case insensitive) and blank names + Arguments.of(DuplicateHeaderMode.DISALLOW, false, true, new String[] {"A", "a", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, true, new String[] {"A", "a", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, true, new String[] {"A", "a", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, true, new String[] {"A", "a", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, true, new String[] {"A", "a", " ", " "}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, true, new String[] {"A", "a", " ", " "}, true), + + // Duplicate non-empty (case insensitive) and null names + Arguments.of(DuplicateHeaderMode.DISALLOW, false, true, new String[] {"A", "a", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, false, true, new String[] {"A", "a", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, false, true, new String[] {"A", "a", null, null}, false), + Arguments.of(DuplicateHeaderMode.DISALLOW, true, true, new String[] {"A", "a", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_EMPTY, true, true, new String[] {"A", "a", null, null}, false), + Arguments.of(DuplicateHeaderMode.ALLOW_ALL, true, true, new String[] {"A", "a", null, null}, true) ); } @@ -191,23 +228,33 @@ public class CSVDuplicateHeaderTest { * Return test cases for duplicate header data for use in CSVFormat. *

* This filters the parsing test data to all cases where the allow missing column - * names flag is true. The allow missing column names is exclusively for parsing. + * names flag is true and ignore header case is false: these flags are exclusively for parsing. * CSVFormat validation applies to both parsing and writing and thus validation - * is less strict and behaves as if the missing column names constraint is absent. - * The filtered data is then returned with the missing column names flag set to both - * true and false for each test case. + * is less strict and behaves as if the allow missing column names constraint and + * the ignore header case behaviour are absent. + * The filtered data is then returned with the parser flags set to both true and false + * for each test case. *

* * @return the stream of arguments */ static Stream duplicateHeaderAllowsMissingColumnsNamesData() { return duplicateHeaderData() - .filter(arg -> Boolean.TRUE.equals(arg.get()[1])) + .filter(arg -> Boolean.TRUE.equals(arg.get()[1]) && Boolean.FALSE.equals(arg.get()[2])) .flatMap(arg -> { - // Return test case with flag as both true and false - final Object[] data = arg.get().clone(); - data[1] = Boolean.FALSE; - return Stream.of(arg, Arguments.of(data)); + // Return test case with flags as all true/false combinations + final Object[][] data = new Object[4][]; + final Boolean[] flags = {Boolean.TRUE, Boolean.FALSE}; + int i = 0; + for (final Boolean a : flags) { + for (final Boolean b : flags) { + data[i] = arg.get().clone(); + data[i][1] = a; + data[i][2] = b; + i++; + } + } + return Arrays.stream(data).map(Arguments::of); }); } @@ -216,6 +263,7 @@ public class CSVDuplicateHeaderTest { * * @param duplicateHeaderMode the duplicate header mode * @param allowMissingColumnNames the allow missing column names flag (only used for parsing) + * @param ignoreHeaderCase the ignore header case flag (only used for parsing) * @param headers the headers * @param valid true if the settings are expected to be valid, otherwise expect a IllegalArgumentException */ @@ -223,12 +271,14 @@ public class CSVDuplicateHeaderTest { @MethodSource(value = {"duplicateHeaderAllowsMissingColumnsNamesData"}) public void testCSVFormat(final DuplicateHeaderMode duplicateHeaderMode, final boolean allowMissingColumnNames, + final boolean ignoreHeaderCase, final String[] headers, final boolean valid) { final CSVFormat.Builder builder = CSVFormat.DEFAULT.builder() .setDuplicateHeaderMode(duplicateHeaderMode) .setAllowMissingColumnNames(allowMissingColumnNames) + .setIgnoreHeaderCase(ignoreHeaderCase) .setHeader(headers); if (valid) { final CSVFormat format = builder.build(); @@ -245,6 +295,7 @@ public class CSVDuplicateHeaderTest { * * @param duplicateHeaderMode the duplicate header mode * @param allowMissingColumnNames the allow missing column names flag (only used for parsing) + * @param ignoreHeaderCase the ignore header case flag (only used for parsing) * @param headers the headers (joined with the CSVFormat delimiter to create a string input) * @param valid true if the settings are expected to be valid, otherwise expect a IllegalArgumentException * @throws IOException Signals that an I/O exception has occurred. @@ -253,17 +304,19 @@ public class CSVDuplicateHeaderTest { @MethodSource(value = {"duplicateHeaderData"}) public void testCSVParser(final DuplicateHeaderMode duplicateHeaderMode, final boolean allowMissingColumnNames, + final boolean ignoreHeaderCase, final String[] headers, final boolean valid) throws IOException { final CSVFormat format = CSVFormat.DEFAULT.builder() .setDuplicateHeaderMode(duplicateHeaderMode) .setAllowMissingColumnNames(allowMissingColumnNames) + .setIgnoreHeaderCase(ignoreHeaderCase) .setNullString("NULL") .setHeader() .build(); final String input = Arrays.stream(headers) - .map(s -> s == null ? "NULL" : s) + .map(s -> s == null ? format.getNullString() : s) .collect(Collectors.joining(format.getDelimiterString())); if (valid) { try(CSVParser parser = CSVParser.parse(input, format)) {