You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2016/04/18 08:16:51 UTC

svn commit: r1739694 - in /commons/proper/csv/trunk/src: changes/ main/java/org/apache/commons/csv/ test/java/org/apache/commons/csv/

Author: ggregory
Date: Mon Apr 18 06:16:51 2016
New Revision: 1739694

URL: http://svn.apache.org/viewvc?rev=1739694&view=rev
Log:
[CSV-175] Support for ignoring trailing delimiter.
[CSV-177] Support trimming leading and trailing blanks.
[CSV-178] Create default formats for Informix UNLOAD and UNLOAD CSV.

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/CSVParser.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/CSVParserTest.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=1739694&r1=1739693&r2=1739694&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/changes/changes.xml (original)
+++ commons/proper/csv/trunk/src/changes/changes.xml Mon Apr 18 06:16:51 2016
@@ -43,9 +43,12 @@
       <action issue="CSV-153" type="update" dev="britter" due-to="Wren">CSVPrinter doesn't skip creation of header record if skipHeaderRecord is set to true</action>
       <action issue="CSV-159" type="add" dev="ggregory" due-to="Yamil Medina">Add IgnoreCase option for accessing header names</action>
       <action issue="CSV-169" type="add" dev="ggregory" due-to="Gary Gregory">The null string should be case-sensitive when reading records</action>
-      <action issue="CSV-168" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CsvFormat.nullString should not be escaped</action>
+      <action issue="CSV-168" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CSVFormat.nullString should not be escaped</action>
       <action issue="CSV-170" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CSVFormat.MYSQL nullString should be "\N"</action>
       <action issue="CSV-161" type="fix" dev="ggregory" due-to="Gary Gregory, Kristof Meixner, Emmanuel Bourg">Fix Javadoc to say CSVFormat with() methods return a new CSVFormat</action>
+      <action issue="CSV-175" type="add" dev="ggregory" due-to="Gary Gregory, Chris Jones">Support for ignoring trailing delimiter.</action>
+      <action issue="CSV-177" type="add" dev="ggregory" due-to="Gary Gregory">Support trimming leading and trailing blanks.</action>
+      <action issue="CSV-178" type="add" dev="ggregory" due-to="Gary Gregory">Create default formats for Informix UNLOAD and UNLOAD CSV.</action>
     </release>
     <release version="1.2" date="2015-08-24" description="Feature and bug fix release">
       <action issue="CSV-145" type="fix" dev="ggregory" due-to="Frank Ulbricht">CSVFormat.with* methods clear the header comments</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=1739694&r1=1739693&r2=1739694&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 Mon Apr 18 06:16:51 2016
@@ -23,6 +23,7 @@ import static org.apache.commons.csv.Con
 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.LF;
+import static org.apache.commons.csv.Constants.PIPE;
 import static org.apache.commons.csv.Constants.TAB;
 
 import java.io.IOException;
@@ -157,17 +158,29 @@ public final class CSVFormat implements
         /**
          * @see CSVFormat#DEFAULT
          */
-        Default(CSVFormat.DEFAULT), 
+        Default(CSVFormat.DEFAULT),
 
         /**
          * @see CSVFormat#EXCEL
          */
-        Excel(CSVFormat.EXCEL), 
+        Excel(CSVFormat.EXCEL),
+
+        /**
+         * @see CSVFormat#INFORMIX_UNLOAD
+         * @since 1.3
+         */
+        InformixUnload(CSVFormat.INFORMIX_UNLOAD),
+
+        /**
+         * @see CSVFormat#INFORMIX_UNLOAD_CSV
+         * @since 1.3
+         */
+        InformixUnloadCsv(CSVFormat.INFORMIX_UNLOAD_CSV),
 
         /**
          * @see CSVFormat#MYSQL
          */
-        MySQL(CSVFormat.MYSQL), 
+        MySQL(CSVFormat.MYSQL),
 
         /**
          * @see CSVFormat#RFC4180
@@ -184,7 +197,7 @@ public final class CSVFormat implements
         private Predefined(final CSVFormat format) {
             this.format = format;
         }
-        
+
         /**
          * Gets the format.
          * 
@@ -194,7 +207,7 @@ public final class CSVFormat implements
             return format;
         }
     };
-    
+
     /**
      * Standard comma separated format, as for {@link #RFC4180} but allowing empty lines.
      *
@@ -207,10 +220,11 @@ public final class CSVFormat implements
      * <li>withRecordSeparator("\r\n")</li>
      * <li>withIgnoreEmptyLines(true)</li>
      * </ul>
+     * 
      * @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);
+    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);
 
     /**
      * Excel file format (using a comma as the value delimiter). Note that the actual value delimiter used by Excel is
@@ -238,11 +252,65 @@ public final class CSVFormat implements
      * Note: this is currently like {@link #RFC4180} plus {@link #withAllowMissingColumnNames(boolean)
      * withAllowMissingColumnNames(true)}.
      * </p>
+     * 
      * @see Predefined#Excel
      */
     public static final CSVFormat EXCEL = DEFAULT.withIgnoreEmptyLines(false).withAllowMissingColumnNames();
 
     /**
+     * Default Informix CSV UNLOAD format used by the {@code UNLOAD TO file_name} operation.
+     *
+     * <p>
+     * This is a comma-delimited format with a LF character as the line separator. Values are not quoted and special
+     * characters are escaped with {@code '\'}. The default NULL string is {@code "\\N"}.
+     * </p>
+     *
+     * <p>
+     * Settings are:
+     * </p>
+     * <ul>
+     * <li>withDelimiter(',')</li>
+     * <li>withQuote("\"")</li>
+     * <li>withRecordSeparator('\n')</li>
+     * <li>withEscape('\\')</li>
+     * </ul>
+     *
+     * @see Predefined#MySQL
+     * @see <a href=
+     *      "http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm">
+     *      http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm</a>
+     * @since 1.3
+     */
+    public static final CSVFormat INFORMIX_UNLOAD = DEFAULT.withDelimiter(PIPE).withEscape(BACKSLASH)
+            .withQuote(DOUBLE_QUOTE_CHAR).withRecordSeparator(LF);
+
+    /**
+     * Default Informix CSV UNLOAD format used by the {@code UNLOAD TO file_name} operation.
+     *
+     * <p>
+     * This is a comma-delimited format with a LF character as the line separator. Values are not quoted and special
+     * characters are escaped with {@code '\'}. The default NULL string is {@code "\\N"}.
+     * </p>
+     *
+     * <p>
+     * Settings are:
+     * </p>
+     * <ul>
+     * <li>withDelimiter(',')</li>
+     * <li>withQuote("\"")</li>
+     * <li>withRecordSeparator('\n')</li>
+     * </ul>
+     *
+     * @see Predefined#MySQL
+     * @see <a href=
+     *      "http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm">
+     *      http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm</a>
+     * @since 1.3
+     */
+    public static final CSVFormat INFORMIX_UNLOAD_CSV = DEFAULT.withDelimiter(COMMA).withQuote(DOUBLE_QUOTE_CHAR)
+            .withRecordSeparator(LF);
+
+    /**
      * Default MySQL format used by the {@code SELECT INTO OUTFILE} and {@code LOAD DATA INFILE} operations.
      *
      * <p>
@@ -263,12 +331,12 @@ public final class CSVFormat implements
      * </ul>
      *
      * @see Predefined#MySQL
-     * @see <a href="http://dev.mysql.com/doc/refman/5.1/en/load-data.html">
-     *      http://dev.mysql.com/doc/refman/5.1/en/load-data.html</a>
+     * @see <a href="http://dev.mysql.com/doc/refman/5.1/en/load-data.html"> http://dev.mysql.com/doc/refman/5.1/en/load
+     *      -data.html</a>
      */
     public static final CSVFormat MYSQL = DEFAULT.withDelimiter(TAB).withEscape(BACKSLASH).withIgnoreEmptyLines(false)
             .withQuote(null).withRecordSeparator(LF).withNullString("\\N");
-    
+
     /**
      * Comma separated format as defined by <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
      *
@@ -281,12 +349,13 @@ public final class CSVFormat implements
      * <li>withRecordSeparator("\r\n")</li>
      * <li>withIgnoreEmptyLines(false)</li>
      * </ul>
+     * 
      * @see Predefined#RFC4180
      */
     public static final CSVFormat RFC4180 = DEFAULT.withIgnoreEmptyLines(false);
-    
+
     private static final long serialVersionUID = 1L;
-    
+
     /**
      * Tab-delimited format.
      *
@@ -299,10 +368,11 @@ public final class CSVFormat implements
      * <li>withRecordSeparator("\r\n")</li>
      * <li>withIgnoreSurroundingSpaces(true)</li>
      * </ul>
+     * 
      * @see Predefined#TDF
      */
     public static final CSVFormat TDF = DEFAULT.withDelimiter(TAB).withIgnoreSurroundingSpaces();
-    
+
     /**
      * Returns true if the given character is a line break character.
      *
@@ -314,7 +384,7 @@ public final class CSVFormat implements
     private static boolean isLineBreak(final char c) {
         return c == LF || c == CR;
     }
-    
+
     /**
      * Returns true if the given character is a line break character.
      *
@@ -326,7 +396,7 @@ public final class CSVFormat implements
     private static boolean isLineBreak(final Character c) {
         return c != null && isLineBreak(c.charValue());
     }
-    
+
     /**
      * Creates a new CSV format with the specified delimiter.
      *
@@ -348,9 +418,10 @@ 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, null, false, false, false);
+        return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, null, false, false,
+                false, false, false);
     }
-    
+
     /**
      * Gets one of the predefined formats from {@link CSVFormat.Predefined}.
      * 
@@ -362,15 +433,15 @@ public final class CSVFormat implements
     public static CSVFormat valueOf(final String format) {
         return CSVFormat.Predefined.valueOf(format).getFormat();
     }
-    
+
     private final boolean allowMissingColumnNames;
-    
+
     private final Character commentMarker; // null if commenting is disabled
-    
+
     private final char delimiter;
-    
+
     private final Character escapeCharacter; // null if escaping is disabled
-    
+
     private final String[] header; // array of header column names
 
     private final String[] headerComments; // array of header comment lines
@@ -391,6 +462,10 @@ public final class CSVFormat implements
 
     private final boolean skipHeaderRecord;
 
+    private final boolean trailingDelimiter;
+
+    private final boolean trim;
+
     /**
      * Creates a customized CSV format.
      *
@@ -422,6 +497,9 @@ public final class CSVFormat implements
      *            TODO
      * @param ignoreHeaderCase
      *            TODO
+     * @param trim
+     *            TODO
+     * @param trailingDelimiter TODO
      * @throws IllegalArgumentException
      *             if the delimiter is a line break character
      */
@@ -429,7 +507,7 @@ public final class CSVFormat implements
             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 allowMissingColumnNames, final boolean ignoreHeaderCase, boolean trim, boolean trailingDelimiter) {
         this.delimiter = delimiter;
         this.quoteCharacter = quoteChar;
         this.quoteMode = quoteMode;
@@ -444,6 +522,8 @@ public final class CSVFormat implements
         this.header = header == null ? null : header.clone();
         this.skipHeaderRecord = skipHeaderRecord;
         this.ignoreHeaderCase = ignoreHeaderCase;
+        this.trailingDelimiter = trailingDelimiter;
+        this.trim = trim;        
         validate();
     }
 
@@ -620,11 +700,9 @@ public final class CSVFormat implements
     /**
      * Gets the String to convert to and from {@code null}.
      * <ul>
-     * <li>
-     * <strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
+     * <li><strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
      * records.</li>
-     * <li>
-     * <strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
+     * <li><strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
      * </ul>
      *
      * @return the String to convert to and from {@code null}. No substitution occurs if {@code null}
@@ -669,6 +747,24 @@ public final class CSVFormat implements
         return skipHeaderRecord;
     }
 
+    /**
+     * Returns whether to add a trailing delimiter.
+     *
+     * @return whether to add a trailing delimiter.
+     */
+    public boolean getTrailingDelimiter() {
+        return trailingDelimiter;
+    }
+
+    /**
+     * Returns whether to trim leading and trailing blanks.
+     *
+     * @return whether to trim leading and trailing blanks.
+     */
+    public boolean getTrim() {
+        return trim;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -827,43 +923,43 @@ public final class CSVFormat implements
         if (isLineBreak(delimiter)) {
             throw new IllegalArgumentException("The delimiter cannot be a line break");
         }
-        
+
         if (quoteCharacter != null && delimiter == quoteCharacter.charValue()) {
-            throw new IllegalArgumentException("The quoteChar character and the delimiter cannot be the same ('" +
-                    quoteCharacter + "')");
+            throw new IllegalArgumentException(
+                    "The quoteChar character and the delimiter cannot be the same ('" + quoteCharacter + "')");
         }
 
         if (escapeCharacter != null && delimiter == escapeCharacter.charValue()) {
-            throw new IllegalArgumentException("The escape character and the delimiter cannot be the same ('" +
-                    escapeCharacter + "')");
+            throw new IllegalArgumentException(
+                    "The escape character and the delimiter cannot be the same ('" + escapeCharacter + "')");
         }
 
         if (commentMarker != null && delimiter == commentMarker.charValue()) {
-            throw new IllegalArgumentException("The comment start character and the delimiter cannot be the same ('" +
-                    commentMarker + "')");
+            throw new IllegalArgumentException(
+                    "The comment start character and the delimiter cannot be the same ('" + commentMarker + "')");
         }
 
         if (quoteCharacter != null && quoteCharacter.equals(commentMarker)) {
-            throw new IllegalArgumentException("The comment start character and the quoteChar cannot be the same ('" +
-                    commentMarker + "')");
+            throw new IllegalArgumentException(
+                    "The comment start character and the quoteChar cannot be the same ('" + commentMarker + "')");
         }
 
         if (escapeCharacter != null && escapeCharacter.equals(commentMarker)) {
-            throw new IllegalArgumentException("The comment start and the escape character cannot be the same ('" +
-                    commentMarker + "')");
+            throw new IllegalArgumentException(
+                    "The comment start and the escape character cannot be the same ('" + commentMarker + "')");
         }
 
         if (escapeCharacter == null && quoteMode == QuoteMode.NONE) {
             throw new IllegalArgumentException("No quotes mode set but no escape character is set");
         }
-        
+
         // validate header
         if (header != null) {
             final Set<String> dupCheck = new HashSet<String>();
             for (final String hdr : header) {
                 if (!dupCheck.add(hdr)) {
-                    throw new IllegalArgumentException("The header contains a duplicate entry: '" + hdr + "' in " +
-                            Arrays.toString(header));
+                    throw new IllegalArgumentException(
+                            "The header contains a duplicate entry: '" + hdr + "' in " + Arrays.toString(header));
                 }
             }
         }
@@ -891,7 +987,7 @@ public final class CSVFormat implements
     public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNames) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -926,7 +1022,7 @@ public final class CSVFormat implements
         }
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -944,7 +1040,7 @@ public final class CSVFormat implements
         }
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -975,12 +1071,12 @@ public final class CSVFormat implements
         }
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape, ignoreSurroundingSpaces,
                 ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord,
-                allowMissingColumnNames, ignoreHeaderCase);
+                allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
-     * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. 
-     * The header can either be parsed automatically from the input file with:
+     * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. The header can
+     * either be parsed automatically from the input file with:
      *
      * <pre>
      * CSVFormat format = aformat.withHeader();
@@ -1009,8 +1105,8 @@ public final class CSVFormat implements
     }
 
     /**
-     * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata.
-     *  The header can either be parsed automatically from the input file with:
+     * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. The header can
+     * either be parsed automatically from the input file with:
      *
      * <pre>
      * CSVFormat format = aformat.withHeader();
@@ -1045,12 +1141,12 @@ public final class CSVFormat implements
         }
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, labels,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
-     * Returns a new {@code CSVFormat} with the header of the format set to the given values. 
-     * The header can either be parsed automatically from the input file with:
+     * Returns a new {@code CSVFormat} with the header of the format set to the given values. The header can either be
+     * parsed automatically from the input file with:
      *
      * <pre>
      * CSVFormat format = aformat.withHeader();
@@ -1074,12 +1170,12 @@ public final class CSVFormat implements
     public CSVFormat withHeader(final String... header) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
-     * Returns a new {@code CSVFormat} with the header comments of the format set to the given values. 
-     * The comments will be printed first, before the headers. This setting is ignored by the parser.
+     * Returns a new {@code CSVFormat} with the header comments of the format set to the given values. The comments will
+     * be printed first, before the headers. This setting is ignored by the parser.
      *
      * <pre>
      * CSVFormat format = aformat.withHeaderComments(&quot;Generated by Apache Commons CSV 1.1.&quot;, new Date());
@@ -1095,7 +1191,7 @@ public final class CSVFormat implements
     public CSVFormat withHeaderComments(final Object... headerComments) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1120,7 +1216,7 @@ public final class CSVFormat implements
     public CSVFormat withIgnoreEmptyLines(final boolean ignoreEmptyLines) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1138,14 +1234,14 @@ public final class CSVFormat implements
      * Returns a new {@code CSVFormat} with whether header names should be accessed ignoring case.
      *
      * @param ignoreHeaderCase
-     *            the case mapping behavior, {@code true} to access name/values, {@code false} to leave the
-     *            mapping as is.
+     *            the case mapping behavior, {@code true} to access name/values, {@code false} to leave the mapping as
+     *            is.
      * @return A new CSVFormat that will ignore case header name if specified as {@code true}
      */
     public CSVFormat withIgnoreHeaderCase(final boolean ignoreHeaderCase) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1170,17 +1266,15 @@ public final class CSVFormat implements
     public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
      * Returns a new {@code CSVFormat} with conversions to and from null for strings on input and output.
      * <ul>
-     * <li>
-     * <strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
+     * <li><strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
      * records.</li>
-     * <li>
-     * <strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
+     * <li><strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
      * </ul>
      *
      * @param nullString
@@ -1191,7 +1285,7 @@ public final class CSVFormat implements
     public CSVFormat withNullString(final String nullString) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1222,7 +1316,7 @@ public final class CSVFormat implements
         }
         return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter, ignoreSurroundingSpaces,
                 ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord,
-                allowMissingColumnNames, ignoreHeaderCase);
+                allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1236,7 +1330,7 @@ public final class CSVFormat implements
     public CSVFormat withQuoteMode(final QuoteMode quoteModePolicy) {
         return new CSVFormat(delimiter, quoteCharacter, quoteModePolicy, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1274,7 +1368,7 @@ public final class CSVFormat implements
     public CSVFormat withRecordSeparator(final String recordSeparator) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
     }
 
     /**
@@ -1301,6 +1395,52 @@ public final class CSVFormat implements
     public CSVFormat withSkipHeaderRecord(final boolean skipHeaderRecord) {
         return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
                 ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
-                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase);
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
+    }
+
+    /**
+     * Returns a new {@code CSVFormat} with whether to trim leading and trailing blanks.
+     *
+     * @param trim
+     *            whether to trim leading and trailing blanks.
+     *
+     * @return A new CSVFormat that is equal to this but with the specified trim setting.
+     */
+    public CSVFormat withTrim(final boolean trim) {
+        return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
+                ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
+    }
+
+    /**
+     * Returns a new {@code CSVFormat} to add a trailing delimiter.
+     *     *
+     * @return A new CSVFormat that is equal to this but with the trailing delimiter setting.
+     */
+    public CSVFormat withTrailingDelimiter() {
+        return withTrailingDelimiter(true);
+    }
+
+    /**
+     * Returns a new {@code CSVFormat} with whether to add a trailing delimiter.
+     *
+     * @param trim
+     *            whether to add a trailing delimiter.
+     *
+     * @return A new CSVFormat that is equal to this but with the specified trailing delimiter setting.
+     */
+    public CSVFormat withTrailingDelimiter(final boolean trailingDelimiter) {
+        return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
+                ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
+                skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
+    }
+
+    /**
+     * Returns a new {@code CSVFormat} to trim leading and trailing blanks.
+     *
+     * @return A new CSVFormat that is equal to this but with the trim setting on.
+     */
+    public CSVFormat withTrim() {
+        return withTrim(true);
     }
 }

Modified: commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java?rev=1739694&r1=1739693&r2=1739694&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java (original)
+++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java Mon Apr 18 06:16:51 2016
@@ -286,10 +286,14 @@ public final class CSVParser implements
         this.recordNumber = recordNumber - 1;
     }
 
-    private void addRecordValue() {
+    private void addRecordValue(boolean lastRecord) {
         final String input = this.reusableToken.content.toString();
+        final String inputClean = this.format.getTrim() ? input.trim() : input;
+        if (lastRecord && inputClean.isEmpty() && this.format.getTrailingDelimiter()) {
+            return;
+        }
         final String nullString = this.format.getNullString();
-        this.record.add(input.equals(nullString) ? null : input);
+        this.record.add(inputClean.equals(nullString) ? null : inputClean);
     }
 
     /**
@@ -497,14 +501,14 @@ public final class CSVParser implements
             this.lexer.nextToken(this.reusableToken);
             switch (this.reusableToken.type) {
             case TOKEN:
-                this.addRecordValue();
+                this.addRecordValue(false);
                 break;
             case EORECORD:
-                this.addRecordValue();
+                this.addRecordValue(true);
                 break;
             case EOF:
                 if (this.reusableToken.isReady) {
-                    this.addRecordValue();
+                    this.addRecordValue(true);
                 }
                 break;
             case INVALID:

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=1739694&r1=1739693&r2=1739694&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 Mon Apr 18 06:16:51 2016
@@ -128,6 +128,7 @@ public final class CSVPrinter implements
         } else {
             strValue = value.toString();
         }
+        strValue = format.getTrim() ? strValue.trim() : strValue;
         this.print(value, strValue, 0, strValue.length());
     }
 
@@ -351,6 +352,9 @@ public final class CSVPrinter implements
      *             If an I/O error occurs
      */
     public void println() throws IOException {
+        if (format.getTrailingDelimiter()) {
+            out.append(format.getDelimiter());            
+        }
         final String recordSeparator = format.getRecordSeparator();
         if (recordSeparator != null) {
             out.append(recordSeparator);

Modified: commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVParserTest.java
URL: http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVParserTest.java?rev=1739694&r1=1739693&r2=1739694&view=diff
==============================================================================
--- commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVParserTest.java (original)
+++ commons/proper/csv/trunk/src/test/java/org/apache/commons/csv/CSVParserTest.java Mon Apr 18 06:16:51 2016
@@ -911,6 +911,30 @@ public class CSVParserTest {
         }
     }
 
+    @Test
+    public void testTrailingDelimiter() throws Exception {
+        final Reader in = new StringReader("a,a,a,\n\"1\",\"2\",\"3\",\nx,y,z,");
+        final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrailingDelimiter()
+                .parse(in).iterator();
+        final CSVRecord record = records.next();
+        assertEquals("1", record.get("X"));
+        assertEquals("2", record.get("Y"));
+        assertEquals("3", record.get("Z"));
+        Assert.assertEquals(3, record.size());
+    }
+
+    @Test
+    public void testTrim() throws Exception {
+        final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");
+        final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrim()
+                .parse(in).iterator();
+        final CSVRecord record = records.next();
+        assertEquals("1", record.get("X"));
+        assertEquals("2", record.get("Y"));
+        assertEquals("3", record.get("Z"));
+        Assert.assertEquals(3, record.size());
+    }
+
     private void validateLineNumbers(final String lineSeparator) throws IOException {
         final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c",
                 CSVFormat.DEFAULT.withRecordSeparator(lineSeparator));

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=1739694&r1=1739693&r2=1739694&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 Mon Apr 18 06:16:51 2016
@@ -50,8 +50,6 @@ public class CSVPrinterTest {
 
     private static final int ITERATIONS_FOR_RANDOM_TEST = 50000;
     
-    private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
-
     private static String printable(final String s) {
         final StringBuilder sb = new StringBuilder();
         for (int i = 0; i < s.length(); i++) {
@@ -65,6 +63,8 @@ public class CSVPrinterTest {
         return sb.toString();
     }
 
+    private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
+
     private void doOneRandom(final CSVFormat format) throws Exception {
         final Random r = new Random();
 
@@ -97,6 +97,31 @@ public class CSVPrinterTest {
         parser.close();
     }
 
+    private void doRandom(final CSVFormat format, final int iter) throws Exception {
+        for (int i = 0; i < iter; i++) {
+            doOneRandom(format);
+        }
+    }
+
+    /**
+     * Converts an input CSV array into expected output values WRT NULLs. NULL strings are converted to null values
+     * because the parser will convert these strings to null.
+     */
+    private <T> T[] expectNulls(final T[] original, final CSVFormat csvFormat) {
+        final T[] fixed = original.clone();
+        for (int i = 0; i < fixed.length; i++) {
+            if (ObjectUtils.equals(csvFormat.getNullString(), fixed[i])) {
+                fixed[i] = null;
+            }
+        }
+        return fixed;
+    }
+
+    private Connection geH2Connection() throws SQLException, ClassNotFoundException {
+        Class.forName("org.h2.Driver");
+        return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", "");
+    }
+
     private String[][] generateLines(final int nLines, final int nCol) {
         final String[][] lines = new String[nLines][];
         for (int i = 0; i < nLines; i++) {
@@ -109,10 +134,18 @@ public class CSVPrinterTest {
         return lines;
     }
 
-    private void doRandom(final CSVFormat format, final int iter) throws Exception {
-        for (int i = 0; i < iter; i++) {
-            doOneRandom(format);
-        }
+    private CSVPrinter printWithHeaderComments(final StringWriter sw, final Date now, final CSVFormat baseFormat)
+            throws IOException {
+        CSVFormat format = baseFormat;
+        // Use withHeaderComments first to test CSV-145
+        format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now);
+        format = format.withCommentMarker('#');
+        format = format.withHeader("Col1", "Col2");
+        final CSVPrinter csvPrinter = format.print(sw);
+        csvPrinter.printRecord("A", "B");
+        csvPrinter.printRecord("C", "D");
+        csvPrinter.close();
+        return csvPrinter;
     }
 
     private String randStr() {
@@ -163,6 +196,58 @@ public class CSVPrinterTest {
         return new String(buf);
     }
 
+    private void setUpTable(final Connection connection) throws SQLException {
+        final Statement statement = connection.createStatement();
+        try {
+            statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
+            statement.execute("insert into TEST values(1, 'r1')");
+            statement.execute("insert into TEST values(2, 'r2')");
+        } finally {
+            statement.close();
+        }
+    }
+
+    @Test
+    public void testDelimeterQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
+        printer.print("a,b,c");
+        printer.print("xyz");
+        assertEquals("'a,b,c',xyz", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testDelimeterQuoteNONE() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        printer.print("a,b,c");
+        printer.print("xyz");
+        assertEquals("a!,b!,c,xyz", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testDelimiterEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null));
+        printer.print("a,b,c");
+        printer.print("xyz");
+        assertEquals("a!,b!,c,xyz", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testDelimiterPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
+        printer.print("a,b,c");
+        printer.print("xyz");
+        assertEquals("a,b,c,xyz", sw.toString());
+        printer.close();
+    }
+
     @Test
     public void testDisabledComment() throws IOException {
         final StringWriter sw = new StringWriter();
@@ -172,6 +257,72 @@ public class CSVPrinterTest {
         assertEquals("", sw.toString());
         printer.close();
     }
+    
+    @Test
+    public void testEOLEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
+        printer.print("a\rb\nc");
+        printer.print("x\fy\bz");
+        assertEquals("a!rb!nc,x\fy\bz", sw.toString());
+        printer.close();
+    }
+    
+    @Test
+    public void testEOLPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
+        printer.print("a\rb\nc");
+        printer.print("x\fy\bz");
+        assertEquals("a\rb\nc,x\fy\bz", sw.toString());
+        printer.close();
+    }
+    
+    @Test
+    public void testEOLQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
+        printer.print("a\rb\nc");
+        printer.print("x\by\fz");
+        assertEquals("'a\rb\nc',x\by\fz", sw.toString());
+        printer.close();
+    }
+    
+    @Test
+    public void testEscapeBackslash() throws IOException {
+        StringWriter sw = new StringWriter();
+        final char quoteChar = '\'';
+        final String eol = "\r\n";
+        CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
+        printer.print("\\");
+        printer.close();
+        assertEquals("'\\'", sw.toString());
+
+        sw = new StringWriter();
+        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
+        printer.print("\\\r");
+        printer.close();
+        assertEquals("'\\\r'", sw.toString());
+
+        sw = new StringWriter();
+        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
+        printer.print("X\\\r");
+        printer.close();
+        assertEquals("'X\\\r'", sw.toString());
+
+        sw = new StringWriter();
+        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
+        printer.printRecord(new Object[] { "\\\r" });
+        printer.close();
+        assertEquals("'\\\r'" + eol, sw.toString());
+
+        sw = new StringWriter();
+        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
+        printer.print("\\\\");
+        printer.close();
+        assertEquals("'\\\\'", sw.toString());
+
+    }
 
     @Test
     public void testExcelPrintAllArrayOfArrays() throws IOException {
@@ -228,77 +379,54 @@ public class CSVPrinterTest {
         printer.close();
     }
 
-    private Connection geH2Connection() throws SQLException, ClassNotFoundException {
-        Class.forName("org.h2.Driver");
-        return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", "");
-    }
-
     @Test
-    @Ignore
-    public void testJira135All() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+    public void testHeader() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        final List<String> list = new LinkedList<String>();
-        list.add("\"");
-        list.add("\n");
-        list.add("\\");
-        printer.printRecord(list);
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3"));
+        printer.printRecord("a", "b", "c");
+        printer.printRecord("x", "y", "z");
+        assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
         printer.close();
-        final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
     }
-    
+
     @Test
-    @Ignore
-    public void testJira135_part3() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+    public void testHeaderCommentExcel() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        final List<String> list = new LinkedList<String>();
-        list.add("\\");
-        printer.printRecord(list);
-        printer.close();
-        final String expected = "\"\\\\\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+        final Date now = new Date();
+        final 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
-    @Ignore
-    public void testJira135_part2() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+    public void testHeaderCommentTdf() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        final List<String> list = new LinkedList<String>();
-        list.add("\n");
-        printer.printRecord(list);
-        printer.close();
-        final String expected = "\"\\n\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+        final Date now = new Date();
+        final 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();
     }
-    
+
     @Test
-    @Ignore
-    public void testJira135_part1() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+    public void testHeaderNotSet() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        final List<String> list = new LinkedList<String>();
-        list.add("\"");
-        printer.printRecord(list);
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
+        printer.printRecord("a", "b", "c");
+        printer.printRecord("x", "y", "z");
+        assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
         printer.close();
-        final String expected = "\"\\\"\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
     }
-    
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFormat() throws Exception {
+        final CSVFormat invalidFormat = CSVFormat.DEFAULT.withDelimiter(CR);
+        new CSVPrinter(new StringWriter(), invalidFormat).close();
+    }
+
     @Test
     public void testJdbcPrinter() throws IOException, ClassNotFoundException, SQLException {
         final StringWriter sw = new StringWriter();
@@ -361,15 +489,70 @@ public class CSVPrinterTest {
         }
     }
 
-    private void setUpTable(final Connection connection) throws SQLException {
-        final Statement statement = connection.createStatement();
-        try {
-            statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
-            statement.execute("insert into TEST values(1, 'r1')");
-            statement.execute("insert into TEST values(2, 'r2')");
-        } finally {
-            statement.close();
-        }
+    @Test
+    @Ignore
+    public void testJira135_part1() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        final List<String> list = new LinkedList<String>();
+        list.add("\"");
+        printer.printRecord(list);
+        printer.close();
+        final String expected = "\"\\\"\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Ignore
+    public void testJira135_part2() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        final List<String> list = new LinkedList<String>();
+        list.add("\n");
+        printer.printRecord(list);
+        printer.close();
+        final String expected = "\"\\n\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Ignore
+    public void testJira135_part3() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        final List<String> list = new LinkedList<String>();
+        list.add("\\");
+        printer.printRecord(list);
+        printer.close();
+        final String expected = "\"\\\\\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Ignore
+    public void testJira135All() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        final List<String> list = new LinkedList<String>();
+        list.add("\"");
+        list.add("\n");
+        list.add("\\");
+        printer.printRecord(list);
+        printer.close();
+        final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
     }
 
     @Test
@@ -383,11 +566,6 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testMySqlNullStringDefault() throws IOException {
-        assertEquals("\\N", CSVFormat.MYSQL.getNullString());
-    }
-
-    @Test
     public void testMySqlNullOutput() throws IOException {
         Object[] s = new String[] { "NULL", null };
         CSVFormat format = CSVFormat.MYSQL.withQuote('"').withNullString("NULL").withQuoteMode(QuoteMode.NON_NUMERIC);
@@ -489,22 +667,85 @@ public class CSVPrinterTest {
         assertArrayEquals(expectNulls(s, format), record0);
     }
 
-    /**
-     * Converts an input CSV array into expected output values WRT NULLs. NULL strings are converted to null values
-     * because the parser will convert these strings to null.
-     */
-    private <T> T[] expectNulls(final T[] original, final CSVFormat csvFormat) {
-        final T[] fixed = original.clone();
-        for (int i = 0; i < fixed.length; i++) {
-            if (ObjectUtils.equals(csvFormat.getNullString(), fixed[i])) {
-                fixed[i] = null;
-            }
-        }
-        return fixed;
+    @Test
+    public void testMySqlNullStringDefault() throws IOException {
+        assertEquals("\\N", CSVFormat.MYSQL.getNullString());
     }
 
-    private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException {
-        return CSVParser.parse(expected, format).getRecords().get(0).values();
+    @Test(expected = IllegalArgumentException.class)
+    public void testNewCsvPrinterAppendableNullFormat() throws Exception {
+        new CSVPrinter(new StringWriter(), null).close();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNewCSVPrinterNullAppendableFormat() throws Exception {
+        new CSVPrinter(null, CSVFormat.DEFAULT).close();
+    }
+
+    @Test
+    public void testParseCustomNullValues() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
+        final CSVPrinter printer = new CSVPrinter(sw, format);
+        printer.printRecord("a", null, "b");
+        printer.close();
+        final String csvString = sw.toString();
+        assertEquals("a,NULL,b" + recordSeparator, csvString);
+        final Iterable<CSVRecord> iterable = format.parse(new StringReader(csvString));
+        final Iterator<CSVRecord> iterator = iterable.iterator();
+        final CSVRecord record = iterator.next();
+        assertEquals("a", record.get(0));
+        assertEquals(null, record.get(1));
+        assertEquals("b", record.get(2));
+        assertFalse(iterator.hasNext());
+        ((CSVParser) iterable).close();
+    }
+
+    @Test
+    public void testPlainEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
+        printer.print("abc");
+        printer.print("xyz");
+        assertEquals("abc,xyz", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testPlainPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
+        printer.print("abc");
+        printer.print("xyz");
+        assertEquals("abc,xyz", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testPlainQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
+        printer.print("abc");
+        assertEquals("abc", sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testPrint() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = CSVFormat.DEFAULT.print(sw);
+        printer.printRecord("a", "b\\c");
+        assertEquals("a,b\\c" + recordSeparator, sw.toString());
+        printer.close();
+    }
+
+    @Test
+    public void testPrintCustomNullValues() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"));
+        printer.printRecord("a", null, "b");
+        assertEquals("a,NULL,b" + recordSeparator, sw.toString());
+        printer.close();
     }
 
     @Test
@@ -571,15 +812,6 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testPrint() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = CSVFormat.DEFAULT.print(sw);
-        printer.printRecord("a", "b\\c");
-        assertEquals("a,b\\c" + recordSeparator, sw.toString());
-        printer.close();
-    }
-
-    @Test
     public void testPrintNullValues() throws IOException {
         final StringWriter sw = new StringWriter();
         final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);
@@ -589,34 +821,6 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testPrintCustomNullValues() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"));
-        printer.printRecord("a", null, "b");
-        assertEquals("a,NULL,b" + recordSeparator, sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testParseCustomNullValues() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        printer.printRecord("a", null, "b");
-        printer.close();
-        final String csvString = sw.toString();
-        assertEquals("a,NULL,b" + recordSeparator, csvString);
-        final Iterable<CSVRecord> iterable = format.parse(new StringReader(csvString));
-        final Iterator<CSVRecord> iterator = iterable.iterator();
-        final CSVRecord record = iterator.next();
-        assertEquals("a", record.get(0));
-        assertEquals(null, record.get(1));
-        assertEquals("b", record.get(2));
-        assertFalse(iterator.hasNext());
-        ((CSVParser) iterable).close();
-    }
-
-    @Test
     public void testQuoteAll() throws IOException {
         final StringWriter sw = new StringWriter();
         final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.ALL));
@@ -650,22 +854,13 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testRandomTdf() throws Exception {
-        doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
     public void testRandomRfc4180() throws Exception {
         doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST);
     }
 
     @Test
-    public void testPlainQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
-        printer.print("abc");
-        assertEquals("abc", sw.toString());
-        printer.close();
+    public void testRandomTdf() throws Exception {
+        doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
     }
 
     @Test
@@ -689,126 +884,11 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testDelimeterQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
-        printer.print("a,b,c");
-        printer.print("xyz");
-        assertEquals("'a,b,c',xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testDelimeterQuoteNONE() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
-        final CSVPrinter printer = new CSVPrinter(sw, format);
-        printer.print("a,b,c");
-        printer.print("xyz");
-        assertEquals("a!,b!,c,xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testEOLQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
-        printer.print("a\rb\nc");
-        printer.print("x\by\fz");
-        assertEquals("'a\rb\nc',x\by\fz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testEscapeBackslash() throws IOException {
-        StringWriter sw = new StringWriter();
-        final char quoteChar = '\'';
-        final String eol = "\r\n";
-        CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
-        printer.print("\\");
-        printer.close();
-        assertEquals("'\\'", sw.toString());
-
-        sw = new StringWriter();
-        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
-        printer.print("\\\r");
-        printer.close();
-        assertEquals("'\\\r'", sw.toString());
-
-        sw = new StringWriter();
-        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
-        printer.print("X\\\r");
-        printer.close();
-        assertEquals("'X\\\r'", sw.toString());
-
-        sw = new StringWriter();
-        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
-        printer.printRecord(new Object[] { "\\\r" });
-        printer.close();
-        assertEquals("'\\\r'" + eol, sw.toString());
-
-        sw = new StringWriter();
-        printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
-        printer.print("\\\\");
-        printer.close();
-        assertEquals("'\\\\'", sw.toString());
-
-    }
-
-    @Test
-    public void testPlainEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
-        printer.print("abc");
-        printer.print("xyz");
-        assertEquals("abc,xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testDelimiterEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null));
-        printer.print("a,b,c");
-        printer.print("xyz");
-        assertEquals("a!,b!,c,xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testEOLEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
-        printer.print("a\rb\nc");
-        printer.print("x\fy\bz");
-        assertEquals("a!rb!nc,x\fy\bz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testPlainPlain() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
-        printer.print("abc");
-        printer.print("xyz");
-        assertEquals("abc,xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testDelimiterPlain() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
-        printer.print("a,b,c");
-        printer.print("xyz");
-        assertEquals("a,b,c,xyz", sw.toString());
-        printer.close();
-    }
-
-    @Test
-    public void testHeader() throws IOException {
+    public void testSkipHeaderRecordFalse() throws IOException {
+        // functionally identical to testHeader, used to test CSV-153
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3"));
+        final CSVPrinter printer = new CSVPrinter(sw,
+                CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(false));
         printer.printRecord("a", "b", "c");
         printer.printRecord("x", "y", "z");
         assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
@@ -816,16 +896,6 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testHeaderNotSet() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
-        printer.printRecord("a", "b", "c");
-        printer.printRecord("x", "y", "z");
-        assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
-        printer.close();
-    }
-
-    @Test
     public void testSkipHeaderRecordTrue() throws IOException {
         // functionally identical to testHeaderNotSet, used to test CSV-153
         final StringWriter sw = new StringWriter();
@@ -838,76 +908,43 @@ public class CSVPrinterTest {
     }
 
     @Test
-    public void testSkipHeaderRecordFalse() throws IOException {
-        // functionally identical to testHeader, used to test CSV-153
+    public void testTrimOnOneColumn() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw,
-                CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(false));
-        printer.printRecord("a", "b", "c");
-        printer.printRecord("x", "y", "z");
-        assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim());
+        printer.print(" A ");
+        assertEquals("A", sw.toString());
         printer.close();
     }
 
     @Test
-    public void testHeaderCommentExcel() throws IOException {
+    public void testTrimOnTwoColumns() throws IOException {
         final StringWriter sw = new StringWriter();
-        final Date now = new Date();
-        final 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();
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim());
+        printer.print(" A ");
+        printer.print(" B ");
+        assertEquals("A,B", sw.toString());
+        printer.close();
     }
 
     @Test
-    public void testHeaderCommentTdf() throws IOException {
+    public void testTrailingDelimiterOnTwoColumns() throws IOException {
         final StringWriter sw = new StringWriter();
-        final Date now = new Date();
-        final 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, final Date now, final CSVFormat baseFormat)
-            throws IOException {
-        CSVFormat format = baseFormat;
-        // Use withHeaderComments first to test CSV-145
-        format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now);
-        format = format.withCommentMarker('#');
-        format = format.withHeader("Col1", "Col2");
-        final CSVPrinter csvPrinter = format.print(sw);
-        csvPrinter.printRecord("A", "B");
-        csvPrinter.printRecord("C", "D");
-        csvPrinter.close();
-        return csvPrinter;
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrailingDelimiter());
+        printer.printRecord("A", "B");
+        assertEquals("A,B,\r\n", sw.toString());
+        printer.close();
     }
 
     @Test
-    public void testEOLPlain() throws IOException {
+    public void testTrimOffOneColumn() throws IOException {
         final StringWriter sw = new StringWriter();
-        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
-        printer.print("a\rb\nc");
-        printer.print("x\fy\bz");
-        assertEquals("a\rb\nc,x\fy\bz", sw.toString());
+        final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim(false));
+        printer.print(" A ");
+        assertEquals("\" A \"", sw.toString());
         printer.close();
     }
 
-    @Test(expected = IllegalArgumentException.class)
-    public void testInvalidFormat() throws Exception {
-        final CSVFormat invalidFormat = CSVFormat.DEFAULT.withDelimiter(CR);
-        new CSVPrinter(new StringWriter(), invalidFormat).close();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testNewCSVPrinterNullAppendableFormat() throws Exception {
-        new CSVPrinter(null, CSVFormat.DEFAULT).close();
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testNewCsvPrinterAppendableNullFormat() throws Exception {
-        new CSVPrinter(new StringWriter(), null).close();
+    private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException {
+        return CSVParser.parse(expected, format).getRecords().get(0).values();
     }
 }