From 0e44eba0b67d35becb82b3fa7ad027acad67262f Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Fri, 2 Oct 2020 22:24:55 +0200 Subject: [PATCH 1/2] csv-264: added duplicateheadermode for flexibility with header strictness --- .../org/apache/commons/csv/CSVFormat.java | 92 +++++++++++------ .../org/apache/commons/csv/CSVParser.java | 14 ++- .../commons/csv/DuplicateHeaderMode.java | 43 ++++++++ .../checkstyle/checkstyle-suppressions.xml | 2 +- .../org/apache/commons/csv/CSVFormatTest.java | 13 +++ .../commons/csv/issues/JiraCsv264Test.java | 98 +++++++++++++++++++ 6 files changed, 224 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java create mode 100644 src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java b/src/main/java/org/apache/commons/csv/CSVFormat.java index 6bf3ed18a7..324829f6b5 100644 --- a/src/main/java/org/apache/commons/csv/CSVFormat.java +++ b/src/main/java/org/apache/commons/csv/CSVFormat.java @@ -17,18 +17,6 @@ package org.apache.commons.csv; -import static org.apache.commons.csv.Constants.BACKSLASH; -import static org.apache.commons.csv.Constants.COMMA; -import static org.apache.commons.csv.Constants.COMMENT; -import static org.apache.commons.csv.Constants.CR; -import static org.apache.commons.csv.Constants.CRLF; -import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; -import static org.apache.commons.csv.Constants.EMPTY; -import static org.apache.commons.csv.Constants.LF; -import static org.apache.commons.csv.Constants.PIPE; -import static org.apache.commons.csv.Constants.SP; -import static org.apache.commons.csv.Constants.TAB; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -48,6 +36,18 @@ import java.util.Objects; import java.util.Set; +import static org.apache.commons.csv.Constants.BACKSLASH; +import static org.apache.commons.csv.Constants.COMMA; +import static org.apache.commons.csv.Constants.COMMENT; +import static org.apache.commons.csv.Constants.CR; +import static org.apache.commons.csv.Constants.CRLF; +import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; +import static org.apache.commons.csv.Constants.EMPTY; +import static org.apache.commons.csv.Constants.LF; +import static org.apache.commons.csv.Constants.PIPE; +import static org.apache.commons.csv.Constants.SP; +import static org.apache.commons.csv.Constants.TAB; + /** * Specifies the format of a CSV file and parses input. * @@ -188,8 +188,6 @@ public static Builder create(final CSVFormat csvFormat) { return new Builder(csvFormat); } - private boolean allowDuplicateHeaderNames; - private boolean allowMissingColumnNames; private boolean autoFlush; @@ -198,6 +196,8 @@ public static Builder create(final CSVFormat csvFormat) { private String delimiter; + private DuplicateHeaderMode duplicateHeaderMode; + private Character escapeCharacter; private String[] headerComments; @@ -245,7 +245,7 @@ private Builder(final CSVFormat csvFormat) { this.trim = csvFormat.trim; this.autoFlush = csvFormat.autoFlush; this.quotedNullString = csvFormat.quotedNullString; - this.allowDuplicateHeaderNames = csvFormat.allowDuplicateHeaderNames; + this.duplicateHeaderMode = csvFormat.duplicateHeaderMode; } /** @@ -262,12 +262,26 @@ public CSVFormat build() { * * @param allowDuplicateHeaderNames the duplicate header names behavior, true to allow, false to disallow. * @return This instance. + * @deprecated Use {@link #setDuplicateHeaderMode(DuplicateHeaderMode)}. */ + @Deprecated public Builder setAllowDuplicateHeaderNames(final boolean allowDuplicateHeaderNames) { - this.allowDuplicateHeaderNames = allowDuplicateHeaderNames; + final DuplicateHeaderMode mode = allowDuplicateHeaderNames ? DuplicateHeaderMode.ALLOW_ALL : DuplicateHeaderMode.ALLOW_EMPTY; + setDuplicateHeaderMode(mode); return this; } + /** + * Sets the duplicate header names behavior. + * + * @param duplicateHeaderMode the duplicate header names behavior + * @return This instance. + */ + public Builder setDuplicateHeaderMode(final DuplicateHeaderMode duplicateHeaderMode) { + this.duplicateHeaderMode = duplicateHeaderMode; + return this; + } + /** * Sets the 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. @@ -760,7 +774,8 @@ public CSVFormat getFormat() { } /** - * Standard Comma Separated Value format, as for {@link #RFC4180} but allowing empty lines. + * Standard Comma Separated Value format, as for {@link #RFC4180} but allowing + * empty lines. * *

* The {@link Builder} settings are: @@ -770,13 +785,13 @@ public CSVFormat getFormat() { *

  • {@code setQuote('"')}
  • *
  • {@code setRecordSeparator("\r\n")}
  • *
  • {@code setIgnoreEmptyLines(true)}
  • - *
  • {@code setAllowDuplicateHeaderNames(true)}
  • + *
  • {@code setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_ALL)}
  • * * * @see Predefined#Default */ public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null, false, true, CRLF, null, null, null, false, false, false, - false, false, false, true); + false, false, false, DuplicateHeaderMode.ALLOW_ALL); /** * Excel file format (using a comma as the value delimiter). Note that the actual value delimiter used by Excel is locale dependent, it might be necessary @@ -799,7 +814,7 @@ public CSVFormat getFormat() { *
  • {@code setRecordSeparator("\r\n")}
  • *
  • {@code setIgnoreEmptyLines(false)}
  • *
  • {@code setAllowMissingColumnNames(true)}
  • - *
  • {@code setAllowDuplicateHeaderNames(true)}
  • + *
  • {@code setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_ALL)}
  • * *

    * Note: This is currently like {@link #RFC4180} plus {@link Builder#setAllowMissingColumnNames(boolean) Builder#setAllowMissingColumnNames(true)} and @@ -1220,7 +1235,7 @@ private static boolean isLineBreak(final Character c) { */ public static CSVFormat newFormat(final char delimiter) { return new CSVFormat(String.valueOf(delimiter), null, null, null, null, false, false, null, null, null, null, false, false, false, false, false, false, - true); + DuplicateHeaderMode.ALLOW_ALL); } static String[] toStringArray(final Object[] values) { @@ -1262,7 +1277,7 @@ public static CSVFormat valueOf(final String format) { return CSVFormat.Predefined.valueOf(format).getFormat(); } - private final boolean allowDuplicateHeaderNames; + private final DuplicateHeaderMode duplicateHeaderMode; private final boolean allowMissingColumnNames; @@ -1319,7 +1334,7 @@ private CSVFormat(final Builder builder) { this.trim = builder.trim; this.autoFlush = builder.autoFlush; this.quotedNullString = builder.quotedNullString; - this.allowDuplicateHeaderNames = builder.allowDuplicateHeaderNames; + this.duplicateHeaderMode = builder.duplicateHeaderMode; validate(); } @@ -1343,14 +1358,14 @@ private CSVFormat(final Builder builder) { * @param trim TODO Doc me. * @param trailingDelimiter TODO Doc me. * @param autoFlush TODO Doc me. - * @param allowDuplicateHeaderNames TODO Doc me. + * @param duplicateHeaderMode the behavior when handling duplicate headers * @throws IllegalArgumentException if the delimiter is a line break character. */ private CSVFormat(final String delimiter, final Character quoteChar, final QuoteMode quoteMode, final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String recordSeparator, final String nullString, final Object[] headerComments, final String[] header, final boolean skipHeaderRecord, final boolean allowMissingColumnNames, final boolean ignoreHeaderCase, final boolean trim, final boolean trailingDelimiter, final boolean autoFlush, - final boolean allowDuplicateHeaderNames) { + final DuplicateHeaderMode duplicateHeaderMode) { this.delimiter = delimiter; this.quoteCharacter = quoteChar; this.quoteMode = quoteMode; @@ -1369,7 +1384,7 @@ private CSVFormat(final String delimiter, final Character quoteChar, final Quote this.trim = trim; this.autoFlush = autoFlush; this.quotedNullString = quoteCharacter + nullString + quoteCharacter; - this.allowDuplicateHeaderNames = allowDuplicateHeaderNames; + this.duplicateHeaderMode = duplicateHeaderMode; validate(); } @@ -1416,7 +1431,7 @@ public boolean equals(final Object obj) { return false; } final CSVFormat other = (CSVFormat) obj; - return allowDuplicateHeaderNames == other.allowDuplicateHeaderNames && allowMissingColumnNames == other.allowMissingColumnNames && + 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) && Arrays.equals(headerComments, other.headerComments) && ignoreEmptyLines == other.ignoreEmptyLines && @@ -1450,9 +1465,21 @@ public String format(final Object... values) { * * @return whether duplicate header names are allowed * @since 1.7 + * @deprecated Use {@link #getDuplicateHeaderMode()}. */ + @Deprecated public boolean getAllowDuplicateHeaderNames() { - return allowDuplicateHeaderNames; + return duplicateHeaderMode == DuplicateHeaderMode.ALLOW_ALL; + } + + /** + * Returns how duplicate headers are handled. + * + * @return if duplicate header values are allowed, allowed conditionally, or disallowed. + * @since 1.9 + */ + public DuplicateHeaderMode getDuplicateHeaderMode() { + return duplicateHeaderMode; } /** @@ -1633,7 +1660,7 @@ public int hashCode() { int result = 1; result = prime * result + Arrays.hashCode(header); result = prime * result + Arrays.hashCode(headerComments); - return prime * result + Objects.hash(allowDuplicateHeaderNames, allowMissingColumnNames, autoFlush, commentMarker, delimiter, escapeCharacter, + return prime * result + Objects.hash(duplicateHeaderMode, allowMissingColumnNames, autoFlush, commentMarker, delimiter, escapeCharacter, ignoreEmptyLines, ignoreHeaderCase, ignoreSurroundingSpaces, nullString, quoteCharacter, quoteMode, quotedNullString, recordSeparator, skipHeaderRecord, trailingDelimiter, trim); } @@ -2235,7 +2262,7 @@ private void validate() throws IllegalArgumentException { } // validate header - if (header != null && !allowDuplicateHeaderNames) { + if (header != null && duplicateHeaderMode != DuplicateHeaderMode.ALLOW_ALL) { final Set dupCheck = new HashSet<>(); for (final String hdr : header) { if (!dupCheck.add(hdr)) { @@ -2254,7 +2281,7 @@ private void validate() throws IllegalArgumentException { */ @Deprecated public CSVFormat withAllowDuplicateHeaderNames() { - return builder().setAllowDuplicateHeaderNames(true).build(); + return builder().setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_ALL).build(); } /** @@ -2267,7 +2294,8 @@ public CSVFormat withAllowDuplicateHeaderNames() { */ @Deprecated public CSVFormat withAllowDuplicateHeaderNames(final boolean allowDuplicateHeaderNames) { - return builder().setAllowDuplicateHeaderNames(allowDuplicateHeaderNames).build(); + final DuplicateHeaderMode mode = allowDuplicateHeaderNames ? DuplicateHeaderMode.ALLOW_ALL : DuplicateHeaderMode.ALLOW_EMPTY; + return builder().setDuplicateHeaderMode(mode).build(); } /** diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java b/src/main/java/org/apache/commons/csv/CSVParser.java index 60ecc7374a..af1100d391 100644 --- a/src/main/java/org/apache/commons/csv/CSVParser.java +++ b/src/main/java/org/apache/commons/csv/CSVParser.java @@ -17,8 +17,6 @@ package org.apache.commons.csv; -import static org.apache.commons.csv.Token.Type.TOKEN; - import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -45,6 +43,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import static org.apache.commons.csv.Token.Type.TOKEN; + /** * Parses CSV files according to the specified format. * @@ -497,12 +497,16 @@ private Headers createHeaders() throws IOException { throw new IllegalArgumentException( "A header name is missing in " + Arrays.toString(headerRecord)); } - // Note: This will always allow a duplicate header if the header is empty + final boolean containsHeader = header != null && hdrMap.containsKey(header); - if (containsHeader && !emptyHeader && !this.format.getAllowDuplicateHeaderNames()) { + final DuplicateHeaderMode headerMode = this.format.getDuplicateHeaderMode(); + final boolean duplicatesAllowed = headerMode == DuplicateHeaderMode.ALLOW_ALL; + final boolean emptyDuplicatesAllowed = headerMode == DuplicateHeaderMode.ALLOW_EMPTY; + + if (containsHeader && !duplicatesAllowed && !(emptyHeader && emptyDuplicatesAllowed)) { throw new IllegalArgumentException( String.format( - "The header contains a duplicate name: \"%s\" in %s. If this is valid then use CSVFormat.withAllowDuplicateHeaderNames().", + "The header contains a duplicate name: \"%s\" in %s. If this is valid then use CSVFormat.Builder.setDuplicateHeaderMode().", header, Arrays.toString(headerRecord))); } if (header != null) { diff --git a/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java b/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java new file mode 100644 index 0000000000..1f42dd500d --- /dev/null +++ b/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.csv; + +/** + * Determines how duplicate header fields should be handled + * if {@link CSVFormat#withHeader(String...)} is not null. + * + * @since 1.9 + */ +public enum DuplicateHeaderMode { + + /** + * Allows all duplicate headings. + */ + ALLOW_ALL, + + /** + * Allows duplicate headings only if they're empty + * strings or null. + */ + ALLOW_EMPTY, + + /** + * Disallows duplicate headings entirely. + */ + DISALLOW +} diff --git a/src/site/resources/checkstyle/checkstyle-suppressions.xml b/src/site/resources/checkstyle/checkstyle-suppressions.xml index 402525fd84..abff74c8c5 100644 --- a/src/site/resources/checkstyle/checkstyle-suppressions.xml +++ b/src/site/resources/checkstyle/checkstyle-suppressions.xml @@ -19,5 +19,5 @@ "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - + diff --git a/src/test/java/org/apache/commons/csv/CSVFormatTest.java b/src/test/java/org/apache/commons/csv/CSVFormatTest.java index 682764f947..f7c32bd6d4 100644 --- a/src/test/java/org/apache/commons/csv/CSVFormatTest.java +++ b/src/test/java/org/apache/commons/csv/CSVFormatTest.java @@ -260,6 +260,10 @@ public void testEqualsHash() throws Exception { final Object a = method.invoke(CSVFormat.DEFAULT, QuoteMode.MINIMAL); final Object b = method.invoke(CSVFormat.DEFAULT, QuoteMode.ALL); assertNotEquals(name, type, a, b); + } else if ("org.apache.commons.csv.DuplicateHeaderMode".equals(type)) { + final Object a = method.invoke(CSVFormat.DEFAULT, new Object[] {DuplicateHeaderMode.ALLOW_ALL}); + final Object b = method.invoke(CSVFormat.DEFAULT, new Object[] {DuplicateHeaderMode.DISALLOW}); + assertNotEquals(name, type, a, b); } else if ("java.lang.Object[]".equals(type)){ final Object a = method.invoke(CSVFormat.DEFAULT, new Object[] {new Object[] {null, null}}); final Object b = method.invoke(CSVFormat.DEFAULT, new Object[] {new Object[] {new Object(), new Object()}}); @@ -1295,6 +1299,15 @@ public void testWithEscape() { } + @Test + public void testWithEmptyDuplicates() { + final CSVFormat formatWithEmptyDuplicates = + CSVFormat.DEFAULT.builder().setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_EMPTY).build(); + + assertEquals(DuplicateHeaderMode.ALLOW_EMPTY, formatWithEmptyDuplicates.getDuplicateHeaderMode()); + assertFalse(formatWithEmptyDuplicates.getAllowDuplicateHeaderNames()); + } + @Test public void testWithEscapeCRThrowsExceptions() { assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withEscape(CR)); diff --git a/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java b/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java new file mode 100644 index 0000000000..a2e22aedf9 --- /dev/null +++ b/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.csv.issues; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.DuplicateHeaderMode; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.StringReader; + +/** + * When {@link CSVFormat#withHeader(String...)} is not null; + * duplicate headers with empty strings should not be allowed. + * + * @see Jira Ticker + */ +public class JiraCsv264Test { + + private static final String CSV_STRING = "\"\",\"B\",\"\"\n" + + "\"1\",\"2\",\"3\"\n" + + "\"4\",\"5\",\"6\""; + + /** + * A CSV file with a random gap in the middle. + */ + private static final String CSV_STRING_GAP = "\"A\",\"B\",\"\",\"\",\"E\"\n" + + "\"1\",\"2\",\"\",\"\",\"5\"\n" + + "\"6\",\"7\",\"\",\"\",\"10\""; + + @Test + public void testJiraCsv264() throws IOException { + final CSVFormat csvFormat = CSVFormat.DEFAULT + .builder() + .setHeader() + .setDuplicateHeaderMode(DuplicateHeaderMode.DISALLOW) + .setAllowMissingColumnNames(true) + .build(); + + try (StringReader reader = new StringReader(CSV_STRING)) { + try { + csvFormat.parse(reader); + Assertions.fail("This shouldn't parse the CSV string successfully."); + } catch (IllegalArgumentException ex) { + // Test is successful! + } + } + } + + @Test + public void testJiraCsv264WithGapAllowEmpty() throws IOException { + final CSVFormat csvFormat = CSVFormat.DEFAULT + .builder() + .setHeader() + .setDuplicateHeaderMode(DuplicateHeaderMode.ALLOW_EMPTY) + .setAllowMissingColumnNames(true) + .build(); + + try (StringReader reader = new StringReader(CSV_STRING_GAP)) { + csvFormat.parse(reader); + } + } + + @Test + public void testJiraCsv264WithGapDisallow() throws IOException { + final CSVFormat csvFormat = CSVFormat.DEFAULT + .builder() + .setHeader() + .setDuplicateHeaderMode(DuplicateHeaderMode.DISALLOW) + .setAllowMissingColumnNames(true) + .build(); + + try (StringReader reader = new StringReader(CSV_STRING_GAP)) { + try { + csvFormat.parse(reader); + Assertions.fail("This shouldn't parse the CSV string successfully."); + } catch (IllegalArgumentException ex) { + // Test is successful! + } + } + } +} From 511f2b49f5db1d60866f08664cc09f85427f2c81 Mon Sep 17 00:00:00 2001 From: Seth Falco Date: Fri, 9 Jul 2021 16:23:51 +0200 Subject: [PATCH 2/2] fix: use assertthrows and update docs --- .../org/apache/commons/csv/CSVFormat.java | 28 +++++++++---------- .../org/apache/commons/csv/CSVParser.java | 4 +-- .../commons/csv/DuplicateHeaderMode.java | 9 +++--- .../commons/csv/issues/JiraCsv264Test.java | 25 ++++++----------- 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java b/src/main/java/org/apache/commons/csv/CSVFormat.java index 324829f6b5..77a009dc13 100644 --- a/src/main/java/org/apache/commons/csv/CSVFormat.java +++ b/src/main/java/org/apache/commons/csv/CSVFormat.java @@ -17,6 +17,18 @@ package org.apache.commons.csv; +import static org.apache.commons.csv.Constants.BACKSLASH; +import static org.apache.commons.csv.Constants.COMMA; +import static org.apache.commons.csv.Constants.COMMENT; +import static org.apache.commons.csv.Constants.CR; +import static org.apache.commons.csv.Constants.CRLF; +import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; +import static org.apache.commons.csv.Constants.EMPTY; +import static org.apache.commons.csv.Constants.LF; +import static org.apache.commons.csv.Constants.PIPE; +import static org.apache.commons.csv.Constants.SP; +import static org.apache.commons.csv.Constants.TAB; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -36,18 +48,6 @@ import java.util.Objects; import java.util.Set; -import static org.apache.commons.csv.Constants.BACKSLASH; -import static org.apache.commons.csv.Constants.COMMA; -import static org.apache.commons.csv.Constants.COMMENT; -import static org.apache.commons.csv.Constants.CR; -import static org.apache.commons.csv.Constants.CRLF; -import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; -import static org.apache.commons.csv.Constants.EMPTY; -import static org.apache.commons.csv.Constants.LF; -import static org.apache.commons.csv.Constants.PIPE; -import static org.apache.commons.csv.Constants.SP; -import static org.apache.commons.csv.Constants.TAB; - /** * Specifies the format of a CSV file and parses input. * @@ -1473,10 +1473,10 @@ public boolean getAllowDuplicateHeaderNames() { } /** - * Returns how duplicate headers are handled. + * Gets how duplicate headers are handled. * * @return if duplicate header values are allowed, allowed conditionally, or disallowed. - * @since 1.9 + * @since 1.9.0 */ public DuplicateHeaderMode getDuplicateHeaderMode() { return duplicateHeaderMode; diff --git a/src/main/java/org/apache/commons/csv/CSVParser.java b/src/main/java/org/apache/commons/csv/CSVParser.java index af1100d391..58cdb146b2 100644 --- a/src/main/java/org/apache/commons/csv/CSVParser.java +++ b/src/main/java/org/apache/commons/csv/CSVParser.java @@ -17,6 +17,8 @@ package org.apache.commons.csv; +import static org.apache.commons.csv.Token.Type.TOKEN; + import java.io.Closeable; import java.io.File; import java.io.IOException; @@ -43,8 +45,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static org.apache.commons.csv.Token.Type.TOKEN; - /** * Parses CSV files according to the specified format. * diff --git a/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java b/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java index 1f42dd500d..e623adaa40 100644 --- a/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java +++ b/src/main/java/org/apache/commons/csv/DuplicateHeaderMode.java @@ -21,23 +21,22 @@ * Determines how duplicate header fields should be handled * if {@link CSVFormat#withHeader(String...)} is not null. * - * @since 1.9 + * @since 1.9.0 */ public enum DuplicateHeaderMode { /** - * Allows all duplicate headings. + * Allows all duplicate headers. */ ALLOW_ALL, /** - * Allows duplicate headings only if they're empty - * strings or null. + * Allows duplicate headers only if they're empty strings or null. */ ALLOW_EMPTY, /** - * Disallows duplicate headings entirely. + * Disallows duplicate headers entirely. */ DISALLOW } diff --git a/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java b/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java index a2e22aedf9..bbbb262454 100644 --- a/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java +++ b/src/test/java/org/apache/commons/csv/issues/JiraCsv264Test.java @@ -19,20 +19,21 @@ import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.DuplicateHeaderMode; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + import java.io.IOException; import java.io.StringReader; /** - * When {@link CSVFormat#withHeader(String...)} is not null; - * duplicate headers with empty strings should not be allowed. + * When {@link CSVFormat#withHeader(String...)} is not null; duplicate headers + * with empty strings should not be allowed. * - * @see Jira Ticker + * @see Jira Ticker */ public class JiraCsv264Test { - + private static final String CSV_STRING = "\"\",\"B\",\"\"\n" + "\"1\",\"2\",\"3\"\n" + "\"4\",\"5\",\"6\""; @@ -54,12 +55,7 @@ public void testJiraCsv264() throws IOException { .build(); try (StringReader reader = new StringReader(CSV_STRING)) { - try { - csvFormat.parse(reader); - Assertions.fail("This shouldn't parse the CSV string successfully."); - } catch (IllegalArgumentException ex) { - // Test is successful! - } + assertThrows(IllegalArgumentException.class, () -> csvFormat.parse(reader)); } } @@ -87,12 +83,7 @@ public void testJiraCsv264WithGapDisallow() throws IOException { .build(); try (StringReader reader = new StringReader(CSV_STRING_GAP)) { - try { - csvFormat.parse(reader); - Assertions.fail("This shouldn't parse the CSV string successfully."); - } catch (IllegalArgumentException ex) { - // Test is successful! - } + assertThrows(IllegalArgumentException.class, () -> csvFormat.parse(reader)); } } }