CSV-264: CSVParser identifies null, empty or blank headers as 'missing'

Duplicate missing header names are handled consistently between
CSVFormat and CSVParser.

Document that the ignore header case flag is for parser behaviour.

Update CSVDuplicateHeaderTest to add the ignore header case flag to
tests. Add test cases with case insensitive duplicates.
This commit is contained in:
aherbert 2022-10-25 17:15:06 +01:00 committed by Alex Herbert
parent 8ad07df0d4
commit 69aa686187
3 changed files with 175 additions and 119 deletions

View File

@ -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

View File

@ -499,6 +499,8 @@ public final class CSVParser implements Iterable<CSVRecord>, 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<CSVRecord>, 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<CSVRecord>, 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) {

View File

@ -46,144 +46,181 @@ public class CSVDuplicateHeaderTest {
*/
static Stream<Arguments> 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.
* <p>
* 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.
* </p>
*
* @return the stream of arguments
*/
static Stream<Arguments> 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)) {