Skip to content

Commit a4f7371

Browse files
committed
[CSV-130] CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/csv/trunk@1623984 13f79535-47bb-0310-9956-ffa450edef68
1 parent 807ddd1 commit a4f7371

4 files changed

Lines changed: 151 additions & 47 deletions

File tree

src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
</properties>
4040
<body>
4141
<release version="1.1" date="2014-mm-dd" description="Feature and bug fix release">
42+
<action issue="CSV-130" type="fix" dev="ggregory" due-to="Sergei Lebedev">CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)</action>
4243
<action issue="CSV-128" type="fix" dev="ggregory">CSVFormat.EXCEL should ignore empty header names</action>
4344
<action issue="CSV-129" type="add" dev="ggregory">Add CSVFormat#with 0-arg methods matching boolean arg methods</action>
4445
<action issue="CSV-132" type="fix" dev="ggregory" due-to="Sascha Szott">Incorrect Javadoc referencing org.apache.commons.csv.CSVFormat withQuote()</action>

src/main/java/org/apache/commons/csv/CSVFormat.java

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ public final class CSVFormat implements Serializable {
157157
private final String recordSeparator; // for outputs
158158
private final String nullString; // the string to be used for null values
159159
private final String[] header; // array of header column names
160+
private final String[] headerComments; // array of header comment lines
160161
private final boolean skipHeaderRecord;
161162

162163
/**
@@ -173,7 +174,7 @@ public final class CSVFormat implements Serializable {
173174
* </ul>
174175
*/
175176
public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null,
176-
false, true, CRLF, null, null, false, false);
177+
false, true, CRLF, null, null, null, false, false);
177178

178179
/**
179180
* Comma separated format as defined by <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
@@ -307,7 +308,7 @@ private static boolean isLineBreak(final Character c) {
307308
* @see #TDF
308309
*/
309310
public static CSVFormat newFormat(final char delimiter) {
310-
return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, false, false);
311+
return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, null, false, false);
311312
}
312313

313314
/**
@@ -331,6 +332,7 @@ public static CSVFormat newFormat(final char delimiter) {
331332
* the line separator to use for output
332333
* @param nullString
333334
* the line separator to use for output
335+
* @param toHeaderComments TODO
334336
* @param header
335337
* the header
336338
* @param skipHeaderRecord TODO
@@ -341,8 +343,8 @@ private CSVFormat(final char delimiter, final Character quoteChar,
341343
final QuoteMode quoteMode, final Character commentStart,
342344
final Character escape, final boolean ignoreSurroundingSpaces,
343345
final boolean ignoreEmptyLines, final String recordSeparator,
344-
final String nullString, final String[] header, final boolean skipHeaderRecord,
345-
final boolean allowMissingColumnNames) {
346+
final String nullString, final Object[] headerComments, final String[] header,
347+
final boolean skipHeaderRecord, final boolean allowMissingColumnNames) {
346348
if (isLineBreak(delimiter)) {
347349
throw new IllegalArgumentException("The delimiter cannot be a line break");
348350
}
@@ -356,6 +358,7 @@ private CSVFormat(final char delimiter, final Character quoteChar,
356358
this.ignoreEmptyLines = ignoreEmptyLines;
357359
this.recordSeparator = recordSeparator;
358360
this.nullString = nullString;
361+
this.headerComments = toStringArray(headerComments);
359362
if (header == null) {
360363
this.header = null;
361364
} else {
@@ -372,6 +375,18 @@ private CSVFormat(final char delimiter, final Character quoteChar,
372375
validate();
373376
}
374377

378+
private String[] toStringArray(Object[] values) {
379+
if (values == null) {
380+
return null;
381+
}
382+
String[] strings = new String[values.length];
383+
for (int i = 0; i < values.length; i++) {
384+
Object value = values[i];
385+
strings[i] = value == null ? null : value.toString();
386+
}
387+
return strings;
388+
}
389+
375390
@Override
376391
public boolean equals(final Object obj) {
377392
if (this == obj) {
@@ -495,6 +510,15 @@ public String[] getHeader() {
495510
return header != null ? header.clone() : null;
496511
}
497512

513+
/**
514+
* Returns a copy of the header comment array.
515+
*
516+
* @return a copy of the header comment array; {@code null} if disabled.
517+
*/
518+
public String[] getHeaderComments() {
519+
return headerComments != null ? headerComments.clone() : null;
520+
}
521+
498522
/**
499523
* Specifies whether missing column names are allowed when parsing the header line.
500524
*
@@ -701,6 +725,10 @@ public String toString() {
701725
sb.append(" SurroundingSpaces:ignored");
702726
}
703727
sb.append(" SkipHeaderRecord:").append(skipHeaderRecord);
728+
if (headerComments != null) {
729+
sb.append(' ');
730+
sb.append("HeaderComments:").append(Arrays.toString(headerComments));
731+
}
704732
if (header != null) {
705733
sb.append(' ');
706734
sb.append("Header:").append(Arrays.toString(header));
@@ -775,8 +803,8 @@ public CSVFormat withCommentMarker(final Character commentMarker) {
775803
throw new IllegalArgumentException("The comment start marker character cannot be a line break");
776804
}
777805
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
778-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
779-
allowMissingColumnNames);
806+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
807+
skipHeaderRecord, allowMissingColumnNames);
780808
}
781809

782810
/**
@@ -793,8 +821,8 @@ public CSVFormat withDelimiter(final char delimiter) {
793821
throw new IllegalArgumentException("The delimiter cannot be a line break");
794822
}
795823
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
796-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
797-
allowMissingColumnNames);
824+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
825+
skipHeaderRecord, allowMissingColumnNames);
798826
}
799827

800828
/**
@@ -824,8 +852,8 @@ public CSVFormat withEscape(final Character escape) {
824852
throw new IllegalArgumentException("The escape character cannot be a line break");
825853
}
826854
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape,
827-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
828-
allowMissingColumnNames);
855+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
856+
skipHeaderRecord, allowMissingColumnNames);
829857
}
830858

831859
/**
@@ -847,8 +875,27 @@ public CSVFormat withEscape(final Character escape) {
847875
*/
848876
public CSVFormat withHeader(final String... header) {
849877
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
850-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
851-
allowMissingColumnNames);
878+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
879+
skipHeaderRecord, allowMissingColumnNames);
880+
}
881+
882+
/**
883+
* Sets the header comments of the format. The comments will be printed first, before the headers.
884+
*
885+
* <pre>
886+
* CSVFormat format = aformat.withHeaderComments("Generated by Apache Commons CSV 1.1.", new Date());</pre>
887+
*
888+
* @param header
889+
* the header, {@code null} if disabled, empty if parsed automatically, user specified otherwise.
890+
*
891+
* @return A new CSVFormat that is equal to this but with the specified header
892+
* @see #withSkipHeaderRecord(boolean)
893+
* @since 1.1
894+
*/
895+
public CSVFormat withHeaderComments(final Object... headerComments) {
896+
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
897+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
898+
skipHeaderRecord, allowMissingColumnNames);
852899
}
853900

854901
/**
@@ -872,8 +919,8 @@ public CSVFormat withAllowMissingColumnNames() {
872919
*/
873920
public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNames) {
874921
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
875-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
876-
allowMissingColumnNames);
922+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
923+
skipHeaderRecord, allowMissingColumnNames);
877924
}
878925

879926
/**
@@ -897,8 +944,8 @@ public CSVFormat withIgnoreEmptyLines() {
897944
*/
898945
public CSVFormat withIgnoreEmptyLines(final boolean ignoreEmptyLines) {
899946
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
900-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
901-
allowMissingColumnNames);
947+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
948+
skipHeaderRecord, allowMissingColumnNames);
902949
}
903950

904951
/**
@@ -922,8 +969,8 @@ public CSVFormat withIgnoreSurroundingSpaces() {
922969
*/
923970
public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces) {
924971
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
925-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
926-
allowMissingColumnNames);
972+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
973+
skipHeaderRecord, allowMissingColumnNames);
927974
}
928975

929976
/**
@@ -943,8 +990,8 @@ public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpac
943990
*/
944991
public CSVFormat withNullString(final String nullString) {
945992
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
946-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
947-
allowMissingColumnNames);
993+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
994+
skipHeaderRecord, allowMissingColumnNames);
948995
}
949996

950997
/**
@@ -974,8 +1021,8 @@ public CSVFormat withQuote(final Character quoteChar) {
9741021
throw new IllegalArgumentException("The quoteChar cannot be a line break");
9751022
}
9761023
return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter,
977-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
978-
allowMissingColumnNames);
1024+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
1025+
skipHeaderRecord, allowMissingColumnNames);
9791026
}
9801027

9811028
/**
@@ -988,8 +1035,8 @@ public CSVFormat withQuote(final Character quoteChar) {
9881035
*/
9891036
public CSVFormat withQuoteMode(final QuoteMode quoteModePolicy) {
9901037
return new CSVFormat(delimiter, quoteCharacter, quoteModePolicy, commentMarker, escapeCharacter,
991-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
992-
allowMissingColumnNames);
1038+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
1039+
skipHeaderRecord, allowMissingColumnNames);
9931040
}
9941041

9951042
/**
@@ -1022,8 +1069,8 @@ public CSVFormat withRecordSeparator(final char recordSeparator) {
10221069
*/
10231070
public CSVFormat withRecordSeparator(final String recordSeparator) {
10241071
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
1025-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
1026-
allowMissingColumnNames);
1072+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
1073+
skipHeaderRecord, allowMissingColumnNames);
10271074
}
10281075

10291076
/**
@@ -1052,7 +1099,7 @@ public CSVFormat withSkipHeaderRecord() {
10521099
*/
10531100
public CSVFormat withSkipHeaderRecord(final boolean skipHeaderRecord) {
10541101
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
1055-
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord,
1056-
allowMissingColumnNames);
1102+
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
1103+
skipHeaderRecord, allowMissingColumnNames);
10571104
}
10581105
}

src/main/java/org/apache/commons/csv/CSVPrinter.java

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,13 @@ public final class CSVPrinter implements Flushable, Closeable {
5050
* </p>
5151
*
5252
* @param out
53-
* stream to which to print. Must not be null.
53+
* stream to which to print. Must not be null.
5454
* @param format
55-
* the CSV format. Must not be null.
55+
* the CSV format. Must not be null.
5656
* @throws IOException
57-
* thrown if the optional header cannot be printed.
57+
* thrown if the optional header cannot be printed.
5858
* @throws IllegalArgumentException
59-
* thrown if the parameters of the format are inconsistent or if either out or format are null.
59+
* thrown if the parameters of the format are inconsistent or if either out or format are null.
6060
*/
6161
public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
6262
Assertions.notNull(out, "out");
@@ -66,6 +66,13 @@ public CSVPrinter(final Appendable out, final CSVFormat format) throws IOExcepti
6666
this.format = format;
6767
// TODO: Is it a good idea to do this here instead of on the first call to a print method?
6868
// It seems a pain to have to track whether the header has already been printed or not.
69+
if (format.getHeaderComments() != null) {
70+
for (String line : format.getHeaderComments()) {
71+
if (line != null) {
72+
this.printComment(line);
73+
}
74+
}
75+
}
6976
if (format.getHeader() != null) {
7077
this.printRecord((Object[]) format.getHeader());
7178
}
@@ -113,8 +120,8 @@ public void print(final Object value) throws IOException {
113120
this.print(value, strValue, 0, strValue.length());
114121
}
115122

116-
private void print(final Object object, final CharSequence value,
117-
final int offset, final int len) throws IOException {
123+
private void print(final Object object, final CharSequence value, final int offset, final int len)
124+
throws IOException {
118125
if (!newRecord) {
119126
out.append(format.getDelimiter());
120127
}
@@ -172,8 +179,8 @@ private void printAndEscape(final CharSequence value, final int offset, final in
172179
* Note: must only be called if quoting is enabled, otherwise will generate NPE
173180
*/
174181
// the original object is needed so can check for Number
175-
private void printAndQuote(final Object object, final CharSequence value,
176-
final int offset, final int len) throws IOException {
182+
private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len)
183+
throws IOException {
177184
boolean quote = false;
178185
int start = offset;
179186
int pos = offset;
@@ -381,11 +388,16 @@ public void printRecord(final Object... values) throws IOException {
381388
/**
382389
* Prints all the objects in the given collection handling nested collections/arrays as records.
383390
*
384-
* <p>If the given collection only contains simple objects, this method will print a single record like
391+
* <p>
392+
* If the given collection only contains simple objects, this method will print a single record like
385393
* {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements
386-
* will each be printed as records using {@link #printRecord(Object...)}.</p>
394+
* will each be printed as records using {@link #printRecord(Object...)}.
395+
* </p>
387396
*
388-
* <p>Given the following data structure:</p>
397+
* <p>
398+
* Given the following data structure:
399+
* </p>
400+
*
389401
* <pre>
390402
* <code>
391403
* List&lt;String[]&gt; data = ...
@@ -395,7 +407,10 @@ public void printRecord(final Object... values) throws IOException {
395407
* </code>
396408
* </pre>
397409
*
398-
* <p>Calling this method will print:</p>
410+
* <p>
411+
* Calling this method will print:
412+
* </p>
413+
*
399414
* <pre>
400415
* <code>
401416
* A, B, C
@@ -424,11 +439,16 @@ public void printRecords(final Iterable<?> values) throws IOException {
424439
/**
425440
* Prints all the objects in the given array handling nested collections/arrays as records.
426441
*
427-
* <p>If the given array only contains simple objects, this method will print a single record like
442+
* <p>
443+
* If the given array only contains simple objects, this method will print a single record like
428444
* {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested
429-
* elements will each be printed as records using {@link #printRecord(Object...)}.</p>
445+
* elements will each be printed as records using {@link #printRecord(Object...)}.
446+
* </p>
430447
*
431-
* <p>Given the following data structure:</p>
448+
* <p>
449+
* Given the following data structure:
450+
* </p>
451+
*
432452
* <pre>
433453
* <code>
434454
* String[][] data = new String[3][]
@@ -438,7 +458,10 @@ public void printRecords(final Iterable<?> values) throws IOException {
438458
* </code>
439459
* </pre>
440460
*
441-
* <p>Calling this method will print:</p>
461+
* <p>
462+
* Calling this method will print:
463+
* </p>
464+
*
442465
* <pre>
443466
* <code>
444467
* A, B, C
@@ -467,11 +490,12 @@ public void printRecords(final Object... values) throws IOException {
467490
/**
468491
* Prints all the objects in the given JDBC result set.
469492
*
470-
* @param resultSet result set
471-
* the values to print.
493+
* @param resultSet
494+
* result set the values to print.
472495
* @throws IOException
473496
* If an I/O error occurs
474-
* @throws SQLException if a database access error occurs
497+
* @throws SQLException
498+
* if a database access error occurs
475499
*/
476500
public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
477501
final int columnCount = resultSet.getMetaData().getColumnCount();

0 commit comments

Comments
 (0)