Author: ggregory Date: Wed Sep 10 12:48:02 2014 New Revision: 1623984 URL: http://svn.apache.org/r1623984 Log: [CSV-130] CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)
Modified: commons/proper/csv/trunk/src/changes/changes.xml commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVPrinterTest.java Modified: commons/proper/csv/trunk/src/changes/changes.xml URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/changes/changes.xml?rev=1623984&r1=1623983&r2=1623984&view=diff ============================================================================== --- commons/proper/csv/trunk/src/changes/changes.xml (original) +++ commons/proper/csv/trunk/src/changes/changes.xml Wed Sep 10 12:48:02 2014 @@ -39,6 +39,7 @@ </properties> <body> <release version="1.1" date="2014-mm-dd" description="Feature and bug fix release"> + <action issue="CSV-130" type="fix" dev="ggregory" due-to="Sergei Lebedev">CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)</action> <action issue="CSV-128" type="fix" dev="ggregory">CSVFormat.EXCEL should ignore empty header names</action> <action issue="CSV-129" type="add" dev="ggregory">Add CSVFormat#with 0-arg methods matching boolean arg methods</action> <action issue="CSV-132" type="fix" dev="ggregory" due-to="Sascha Szott">Incorrect Javadoc referencing org.apache.commons.csv.CSVFormat withQuote()</action> Modified: commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java?rev=1623984&r1=1623983&r2=1623984&view=diff ============================================================================== --- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java (original) +++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVFormat.java Wed Sep 10 12:48:02 2014 @@ -157,6 +157,7 @@ public final class CSVFormat implements private final String recordSeparator; // for outputs private final String nullString; // the string to be used for null values private final String[] header; // array of header column names + private final String[] headerComments; // array of header comment lines private final boolean skipHeaderRecord; /** @@ -173,7 +174,7 @@ public final class CSVFormat implements * </ul> */ public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null, - false, true, CRLF, null, null, false, false); + false, true, CRLF, null, null, null, false, false); /** * Comma separated format as defined by <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>. @@ -307,7 +308,7 @@ public final class CSVFormat implements * @see #TDF */ public static CSVFormat newFormat(final char delimiter) { - return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, false, false); + return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, null, false, false); } /** @@ -331,6 +332,7 @@ public final class CSVFormat implements * the line separator to use for output * @param nullString * the line separator to use for output + * @param toHeaderComments TODO * @param header * the header * @param skipHeaderRecord TODO @@ -341,8 +343,8 @@ public final class CSVFormat implements final QuoteMode quoteMode, final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces, final boolean ignoreEmptyLines, final String recordSeparator, - final String nullString, final String[] header, final boolean skipHeaderRecord, - final boolean allowMissingColumnNames) { + final String nullString, final Object[] headerComments, final String[] header, + final boolean skipHeaderRecord, final boolean allowMissingColumnNames) { if (isLineBreak(delimiter)) { throw new IllegalArgumentException("The delimiter cannot be a line break"); } @@ -356,6 +358,7 @@ public final class CSVFormat implements this.ignoreEmptyLines = ignoreEmptyLines; this.recordSeparator = recordSeparator; this.nullString = nullString; + this.headerComments = toStringArray(headerComments); if (header == null) { this.header = null; } else { @@ -372,6 +375,18 @@ public final class CSVFormat implements validate(); } + private String[] toStringArray(Object[] values) { + if (values == null) { + return null; + } + String[] strings = new String[values.length]; + for (int i = 0; i < values.length; i++) { + Object value = values[i]; + strings[i] = value == null ? null : value.toString(); + } + return strings; + } + @Override public boolean equals(final Object obj) { if (this == obj) { @@ -496,6 +511,15 @@ public final class CSVFormat implements } /** + * Returns a copy of the header comment array. + * + * @return a copy of the header comment array; {@code null} if disabled. + */ + public String[] getHeaderComments() { + return headerComments != null ? headerComments.clone() : null; + } + + /** * Specifies whether missing column names are allowed when parsing the header line. * * @return {@code true} if missing column names are allowed when parsing the header line, {@code false} to throw an @@ -701,6 +725,10 @@ public final class CSVFormat implements sb.append(" SurroundingSpaces:ignored"); } sb.append(" SkipHeaderRecord:").append(skipHeaderRecord); + if (headerComments != null) { + sb.append(' '); + sb.append("HeaderComments:").append(Arrays.toString(headerComments)); + } if (header != null) { sb.append(' '); sb.append("Header:").append(Arrays.toString(header)); @@ -775,8 +803,8 @@ public final class CSVFormat implements throw new IllegalArgumentException("The comment start marker character cannot be a line break"); } return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -793,8 +821,8 @@ public final class CSVFormat implements throw new IllegalArgumentException("The delimiter cannot be a line break"); } return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -824,8 +852,8 @@ public final class CSVFormat implements throw new IllegalArgumentException("The escape character cannot be a line break"); } return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -847,8 +875,27 @@ public final class CSVFormat implements */ public CSVFormat withHeader(final String... header) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); + } + + /** + * Sets the header comments of the format. The comments will be printed first, before the headers. + * + * <pre> + * CSVFormat format = aformat.withHeaderComments("Generated by Apache Commons CSV 1.1.", new Date());</pre> + * + * @param header + * the header, {@code null} if disabled, empty if parsed automatically, user specified otherwise. + * + * @return A new CSVFormat that is equal to this but with the specified header + * @see #withSkipHeaderRecord(boolean) + * @since 1.1 + */ + public CSVFormat withHeaderComments(final Object... headerComments) { + return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -872,8 +919,8 @@ public final class CSVFormat implements */ public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNames) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -897,8 +944,8 @@ public final class CSVFormat implements */ public CSVFormat withIgnoreEmptyLines(final boolean ignoreEmptyLines) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -922,8 +969,8 @@ public final class CSVFormat implements */ public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -943,8 +990,8 @@ public final class CSVFormat implements */ public CSVFormat withNullString(final String nullString) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -974,8 +1021,8 @@ public final class CSVFormat implements throw new IllegalArgumentException("The quoteChar cannot be a line break"); } return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -988,8 +1035,8 @@ public final class CSVFormat implements */ public CSVFormat withQuoteMode(final QuoteMode quoteModePolicy) { return new CSVFormat(delimiter, quoteCharacter, quoteModePolicy, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -1022,8 +1069,8 @@ public final class CSVFormat implements */ public CSVFormat withRecordSeparator(final String recordSeparator) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } /** @@ -1052,7 +1099,7 @@ public final class CSVFormat implements */ public CSVFormat withSkipHeaderRecord(final boolean skipHeaderRecord) { return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, - ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, - allowMissingColumnNames); + ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header, + skipHeaderRecord, allowMissingColumnNames); } } Modified: commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java?rev=1623984&r1=1623983&r2=1623984&view=diff ============================================================================== --- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java (original) +++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVPrinter.java Wed Sep 10 12:48:02 2014 @@ -50,13 +50,13 @@ public final class CSVPrinter implements * </p> * * @param out - * stream to which to print. Must not be null. + * stream to which to print. Must not be null. * @param format - * the CSV format. Must not be null. + * the CSV format. Must not be null. * @throws IOException - * thrown if the optional header cannot be printed. + * thrown if the optional header cannot be printed. * @throws IllegalArgumentException - * thrown if the parameters of the format are inconsistent or if either out or format are null. + * thrown if the parameters of the format are inconsistent or if either out or format are null. */ public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException { Assertions.notNull(out, "out"); @@ -66,6 +66,13 @@ public final class CSVPrinter implements this.format = format; // TODO: Is it a good idea to do this here instead of on the first call to a print method? // It seems a pain to have to track whether the header has already been printed or not. + if (format.getHeaderComments() != null) { + for (String line : format.getHeaderComments()) { + if (line != null) { + this.printComment(line); + } + } + } if (format.getHeader() != null) { this.printRecord((Object[]) format.getHeader()); } @@ -113,8 +120,8 @@ public final class CSVPrinter implements this.print(value, strValue, 0, strValue.length()); } - private void print(final Object object, final CharSequence value, - final int offset, final int len) throws IOException { + private void print(final Object object, final CharSequence value, final int offset, final int len) + throws IOException { if (!newRecord) { out.append(format.getDelimiter()); } @@ -172,8 +179,8 @@ public final class CSVPrinter implements * Note: must only be called if quoting is enabled, otherwise will generate NPE */ // the original object is needed so can check for Number - private void printAndQuote(final Object object, final CharSequence value, - final int offset, final int len) throws IOException { + private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len) + throws IOException { boolean quote = false; int start = offset; int pos = offset; @@ -381,11 +388,16 @@ public final class CSVPrinter implements /** * Prints all the objects in the given collection handling nested collections/arrays as records. * - * <p>If the given collection only contains simple objects, this method will print a single record like + * <p> + * If the given collection only contains simple objects, this method will print a single record like * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements - * will each be printed as records using {@link #printRecord(Object...)}.</p> + * will each be printed as records using {@link #printRecord(Object...)}. + * </p> * - * <p>Given the following data structure:</p> + * <p> + * Given the following data structure: + * </p> + * * <pre> * <code> * List<String[]> data = ... @@ -395,7 +407,10 @@ public final class CSVPrinter implements * </code> * </pre> * - * <p>Calling this method will print:</p> + * <p> + * Calling this method will print: + * </p> + * * <pre> * <code> * A, B, C @@ -424,11 +439,16 @@ public final class CSVPrinter implements /** * Prints all the objects in the given array handling nested collections/arrays as records. * - * <p>If the given array only contains simple objects, this method will print a single record like + * <p> + * If the given array only contains simple objects, this method will print a single record like * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested - * elements will each be printed as records using {@link #printRecord(Object...)}.</p> + * elements will each be printed as records using {@link #printRecord(Object...)}. + * </p> * - * <p>Given the following data structure:</p> + * <p> + * Given the following data structure: + * </p> + * * <pre> * <code> * String[][] data = new String[3][] @@ -438,7 +458,10 @@ public final class CSVPrinter implements * </code> * </pre> * - * <p>Calling this method will print:</p> + * <p> + * Calling this method will print: + * </p> + * * <pre> * <code> * A, B, C @@ -467,11 +490,12 @@ public final class CSVPrinter implements /** * Prints all the objects in the given JDBC result set. * - * @param resultSet result set - * the values to print. + * @param resultSet + * result set the values to print. * @throws IOException * If an I/O error occurs - * @throws SQLException if a database access error occurs + * @throws SQLException + * if a database access error occurs */ public void printRecords(final ResultSet resultSet) throws SQLException, IOException { final int columnCount = resultSet.getMetaData().getColumnCount(); Modified: commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVPrinterTest.java URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVPrinterTest.java?rev=1623984&r1=1623983&r2=1623984&view=diff ============================================================================== --- commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVPrinterTest.java (original) +++ commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVPrinterTest.java Wed Sep 10 12:48:02 2014 @@ -29,6 +29,7 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Random; @@ -497,6 +498,37 @@ public class CSVPrinterTest { } @Test + public void testHeaderCommentExcel() throws IOException { + final StringWriter sw = new StringWriter(); + Date now = new Date(); + CSVFormat format = CSVFormat.EXCEL; + final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format); + assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1,Col2\r\nA,B\r\nC,D\r\n", sw.toString()); + csvPrinter.close(); + } + + @Test + public void testHeaderCommentTdf() throws IOException { + final StringWriter sw = new StringWriter(); + Date now = new Date(); + CSVFormat format = CSVFormat.TDF; + final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format); + assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1\tCol2\r\nA\tB\r\nC\tD\r\n", sw.toString()); + csvPrinter.close(); + } + + private CSVPrinter printWithHeaderComments(final StringWriter sw, Date now, CSVFormat format) + throws IOException { + format = format.withCommentMarker('#').withHeader("Col1", "Col2"); + format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now); + final CSVPrinter csvPrinter = format.print(sw); + csvPrinter.printRecord("A", "B"); + csvPrinter.printRecord("C", "D"); + csvPrinter.close(); + return csvPrinter; + } + + @Test public void testEOLPlain() throws IOException { final StringWriter sw = new StringWriter(); final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));