11package de .siegmar .fastcsv .reader ;
22
3+ import java .util .HashSet ;
4+ import java .util .LinkedHashSet ;
35import java .util .List ;
46import java .util .Objects ;
57import java .util .function .Consumer ;
1820public final class NamedCsvRecordHandler extends AbstractInternalCsvCallbackHandler <NamedCsvRecord > {
1921
2022 private static final String [] EMPTY_HEADER = new String [0 ];
23+ private final boolean allowDuplicateHeader ;
2124 private String [] header ;
2225
2326 private NamedCsvRecordHandler (final int maxFields , final int maxFieldSize , final int maxRecordSize ,
24- final FieldModifier fieldModifier , final List <String > header ) {
27+ final FieldModifier fieldModifier ,
28+ final boolean allowDuplicateHeader , final List <String > header ) {
2529 super (maxFields , maxFieldSize , maxRecordSize , fieldModifier );
30+ this .allowDuplicateHeader = allowDuplicateHeader ;
2631 if (header != null ) {
2732 setHeader (header .toArray (new String [0 ]));
2833 }
@@ -50,7 +55,7 @@ public static NamedCsvRecordHandler of() {
5055 ///
5156 /// @param configurer the configuration, must not be `null`
5257 /// @return the new instance
53- /// @throws NullPointerException if `null` is passed
58+ /// @throws NullPointerException if `null` is passed
5459 /// @throws IllegalArgumentException if argument constraints are violated
5560 /// @see #builder()
5661 public static NamedCsvRecordHandler of (final Consumer <NamedCsvRecordHandlerBuilder > configurer ) {
@@ -60,14 +65,36 @@ public static NamedCsvRecordHandler of(final Consumer<NamedCsvRecordHandlerBuild
6065 return builder .build ();
6166 }
6267
63- private void setHeader (final String ... header ) {
68+ @ SuppressWarnings ("PMD.UseVarargs" )
69+ private void setHeader (final String [] header ) {
6470 Objects .requireNonNull (header , "header must not be null" );
6571 for (final String h : header ) {
6672 Objects .requireNonNull (h , "header element must not be null" );
6773 }
74+
75+ if (!allowDuplicateHeader ) {
76+ checkForDuplicates (header );
77+ }
78+
6879 this .header = header .clone ();
6980 }
7081
82+ @ SuppressWarnings ("PMD.UseVarargs" )
83+ private static void checkForDuplicates (final String [] header ) {
84+ final var duplicateHeaders = new LinkedHashSet <String >();
85+ final var seen = new HashSet <String >();
86+ for (final String h : header ) {
87+ if (!seen .add (h )) {
88+ duplicateHeaders .add (h );
89+ }
90+ }
91+
92+ if (!duplicateHeaders .isEmpty ()) {
93+ throw new IllegalArgumentException ("Header contains duplicate fields: "
94+ + duplicateHeaders );
95+ }
96+ }
97+
7198 @ Override
7299 protected NamedCsvRecord buildRecord () {
73100 if (comment ) {
@@ -83,15 +110,29 @@ protected NamedCsvRecord buildRecord() {
83110 }
84111
85112 /// A builder for [NamedCsvRecordHandler].
86- @ SuppressWarnings (" PMD.AvoidFieldNameMatchingMethodName" )
113+ @ SuppressWarnings ({ "checkstyle:HiddenField" , " PMD.AvoidFieldNameMatchingMethodName"} )
87114 public static final class NamedCsvRecordHandlerBuilder
88115 extends AbstractInternalCsvCallbackHandlerBuilder <NamedCsvRecordHandlerBuilder > {
89116
117+ private boolean allowDuplicateHeader ;
90118 private List <String > header ;
91119
92120 private NamedCsvRecordHandlerBuilder () {
93121 }
94122
123+ /// Sets whether duplicate header fields are allowed.
124+ ///
125+ /// When set to `false`, an [IllegalArgumentException] is thrown if the header contains duplicate fields.
126+ /// When set to `true`, duplicate fields are allowed. See [NamedCsvRecord] for details on how duplicate
127+ /// headers are handled.
128+ ///
129+ /// @param allowDuplicateHeader whether duplicate header fields are allowed (default: `false`)
130+ /// @return This updated object, allowing additional method calls to be chained together.
131+ public NamedCsvRecordHandlerBuilder allowDuplicateHeader (final boolean allowDuplicateHeader ) {
132+ this .allowDuplicateHeader = allowDuplicateHeader ;
133+ return this ;
134+ }
135+
95136 /// Sets a predefined header.
96137 ///
97138 /// When not set, the header is taken from the first record (that is not a comment).
@@ -133,7 +174,8 @@ protected NamedCsvRecordHandlerBuilder self() {
133174 /// @throws IllegalArgumentException if argument constraints are violated
134175 /// (see [AbstractInternalCsvCallbackHandler])
135176 public NamedCsvRecordHandler build () {
136- return new NamedCsvRecordHandler (maxFields , maxFieldSize , maxRecordSize , fieldModifier , header );
177+ return new NamedCsvRecordHandler (maxFields , maxFieldSize , maxRecordSize , fieldModifier ,
178+ allowDuplicateHeader , header );
137179 }
138180
139181 }
0 commit comments