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 2022/08/06 20:51:44 UTC

[commons-csv] 01/03: Add CSVPrinter.printRecord[s](Stream).

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-csv.git

commit 8f7e3a668239eea6e2f1ba75bddb800b96f326dd
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Aug 6 16:40:05 2022 -0400

    Add CSVPrinter.printRecord[s](Stream).
---
 src/changes/changes.xml                            |    1 +
 .../java/org/apache/commons/csv/CSVPrinter.java    |  919 +++---
 .../org/apache/commons/csv/CSVPrinterTest.java     | 3452 ++++++++++----------
 3 files changed, 2250 insertions(+), 2122 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 6ddee3ec..8e96d1e0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -50,6 +50,7 @@
       <action issue="CSV-291" type="add" dev="ggregory" due-to="Gary Gregory">Make CSVRecord#values() public.</action>
       <action issue="CSV-264" type="add" dev="ggregory" due-to="Sagar Tiwari, Seth Falco, Alex Herbert, Gary Gregory">Add DuplicateHeaderMode for flexibility with header strictness. #114.</action>
       <action issue="CSV-295" type="add" dev="ggregory" due-to="Gary Gregory">Support for parallelism in CSVPrinter.</action>
+      <action issue="CSV-295" type="add" dev="ggregory" due-to="Gary Gregory">Add CSVPrinter.printRecord[s](Stream).</action>
       <action type="add" dev="ggregory">Add github/codeql-action.</action>
       <!-- UPDATE -->
       <action                 type="update" dev="kinow" due-to="Dependabot, Gary Gregory">Bump actions/cache from 2.1.6 to 3.0.6 #196, #233, #243.</action>
diff --git a/src/main/java/org/apache/commons/csv/CSVPrinter.java b/src/main/java/org/apache/commons/csv/CSVPrinter.java
index 9dcb95cd..6cefff9e 100644
--- a/src/main/java/org/apache/commons/csv/CSVPrinter.java
+++ b/src/main/java/org/apache/commons/csv/CSVPrinter.java
@@ -1,412 +1,507 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.csv;
-
-import static org.apache.commons.csv.Constants.CR;
-import static org.apache.commons.csv.Constants.LF;
-import static org.apache.commons.csv.Constants.SP;
-
-import java.io.Closeable;
-import java.io.Flushable;
-import java.io.IOException;
-import java.sql.Clob;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * Prints values in a {@link CSVFormat CSV format}.
- *
- * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
- * Values are printed according to {@link String#valueOf(Object)}.
- * To complete a record the {@link #println()} method has to be called.
- * Comments can be appended by calling {@link #printComment(String)}.
- * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
- * </p>
- *
- * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
- * or {@link #printRecord(Iterable)}.
- * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
- * methods can be used to print several records at once.
- * </p>
- *
- * <p>Example:</p>
- *
- * <pre>
- * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
- *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
- *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
- *     printer.println();
- *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
- * } catch (IOException ex) {
- *     ex.printStackTrace();
- * }
- * </pre>
- *
- * <p>This code will write the following to csv.txt:</p>
- * <pre>
- * id,userName,firstName,lastName,birthday
- * 1,john73,John,Doe,1973-09-15
- *
- * 2,mary,Mary,Meyer,1985-03-29
- * </pre>
- */
-public final class CSVPrinter implements Flushable, Closeable {
-
-    /** The place that the values get written. */
-    private final Appendable appendable;
-    private final CSVFormat format;
-
-    /** True if we just began a new record. */
-    private boolean newRecord = true;
-
-    /**
-     * Creates a printer that will print values to the given stream following the CSVFormat.
-     * <p>
-     * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
-     * and escaping with a different character) are not supported.
-     * </p>
-     *
-     * @param appendable
-     *            stream to which to print. Must not be null.
-     * @param format
-     *            the CSV format. Must not be null.
-     * @throws IOException
-     *             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.
-     */
-    public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException {
-        Objects.requireNonNull(appendable, "appendable");
-        Objects.requireNonNull(format, "format");
-
-        this.appendable = appendable;
-        this.format = format.copy();
-        // 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 (final String line : format.getHeaderComments()) {
-                this.printComment(line);
-            }
-        }
-        if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
-            this.printRecord((Object[]) format.getHeader());
-        }
-    }
-
-    @Override
-    public void close() throws IOException {
-        close(false);
-    }
-
-    /**
-     * Closes the underlying stream with an optional flush first.
-     * @param flush whether to flush before the actual close.
-     *
-     * @throws IOException
-     *             If an I/O error occurs
-     * @since 1.6
-     */
-    public void close(final boolean flush) throws IOException {
-        if (flush || format.getAutoFlush()) {
-            flush();
-        }
-        if (appendable instanceof Closeable) {
-            ((Closeable) appendable).close();
-        }
-    }
-
-    /**
-     * Flushes the underlying stream.
-     *
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    @Override
-    public void flush() throws IOException {
-        if (appendable instanceof Flushable) {
-            ((Flushable) appendable).flush();
-        }
-    }
-
-    /**
-     * Gets the target Appendable.
-     *
-     * @return the target Appendable.
-     */
-    public Appendable getOut() {
-        return this.appendable;
-    }
-
-    /**
-     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
-     *
-     * @param value
-     *            value to be output.
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public synchronized void print(final Object value) throws IOException {
-        format.print(value, appendable, newRecord);
-        newRecord = false;
-    }
-
-    /**
-     * Prints a comment on a new line among the delimiter separated values.
-     *
-     * <p>
-     * Comments will always begin on a new line and occupy at least one full line. The character specified to start
-     * comments and a space will be inserted at the beginning of each new line in the comment.
-     * </p>
-     *
-     * <p>
-     * If comments are disabled in the current CSV format this method does nothing.
-     * </p>
-     *
-     * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
-     * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
-     * line breaks as record separator.</p>
-     *
-     * @param comment
-     *            the comment to output
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public synchronized void printComment(final String comment) throws IOException {
-        if (comment == null || !format.isCommentMarkerSet()) {
-            return;
-        }
-        if (!newRecord) {
-            println();
-        }
-        appendable.append(format.getCommentMarker().charValue());
-        appendable.append(SP);
-        for (int i = 0; i < comment.length(); i++) {
-            final char c = comment.charAt(i);
-            switch (c) {
-            case CR:
-                if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
-                    i++;
-                }
-                //$FALL-THROUGH$ break intentionally excluded.
-            case LF:
-                println();
-                appendable.append(format.getCommentMarker().charValue());
-                appendable.append(SP);
-                break;
-            default:
-                appendable.append(c);
-                break;
-            }
-        }
-        println();
-    }
-
-    /**
-     * Prints headers for a result set based on its metadata.
-     *
-     * @param resultSet The result set to query for metadata.
-     * @throws IOException If an I/O error occurs.
-     * @throws SQLException If a database access error occurs or this method is called on a closed result set.
-     * @since 1.9.0
-     */
-    public synchronized void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
-        printRecord((Object[]) format.builder().setHeader(resultSet).build().getHeader());
-    }
-
-    /**
-     * Outputs the record separator.
-     *
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public synchronized void println() throws IOException {
-        format.println(appendable);
-        newRecord = true;
-    }
-
-    /**
-     * Prints the given values a single record of delimiter separated values followed by the record separator.
-     *
-     * <p>
-     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
-     * separator to the output after printing the record, so there is no need to call {@link #println()}.
-     * </p>
-     *
-     * @param values
-     *            values to output.
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public synchronized void printRecord(final Iterable<?> values) throws IOException {
-        for (final Object value : values) {
-            print(value);
-        }
-        println();
-    }
-
-    /**
-     * Prints the given values a single record of delimiter separated values followed by the record separator.
-     *
-     * <p>
-     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
-     * separator to the output after printing the record, so there is no need to call {@link #println()}.
-     * </p>
-     *
-     * @param values
-     *            values to output.
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public void printRecord(final Object... values) throws IOException {
-        printRecord(Arrays.asList(values));
-    }
-
-    /**
-     * 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
-     * {@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>
-     *
-     * <p>
-     * Given the following data structure:
-     * </p>
-     *
-     * <pre>
-     * <code>
-     * List&lt;String[]&gt; data = ...
-     * data.add(new String[]{ "A", "B", "C" });
-     * data.add(new String[]{ "1", "2", "3" });
-     * data.add(new String[]{ "A1", "B2", "C3" });
-     * </code>
-     * </pre>
-     *
-     * <p>
-     * Calling this method will print:
-     * </p>
-     *
-     * <pre>
-     * <code>
-     * A, B, C
-     * 1, 2, 3
-     * A1, B2, C3
-     * </code>
-     * </pre>
-     *
-     * @param values
-     *            the values to print.
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public void printRecords(final Iterable<?> values) throws IOException {
-        for (final Object value : values) {
-            if (value instanceof Object[]) {
-                this.printRecord((Object[]) value);
-            } else if (value instanceof Iterable) {
-                this.printRecord((Iterable<?>) value);
-            } else {
-                this.printRecord(value);
-            }
-        }
-    }
-
-    /**
-     * 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
-     * {@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>
-     *
-     * <p>
-     * Given the following data structure:
-     * </p>
-     *
-     * <pre>
-     * <code>
-     * String[][] data = new String[3][]
-     * data[0] = String[]{ "A", "B", "C" };
-     * data[1] = new String[]{ "1", "2", "3" };
-     * data[2] = new String[]{ "A1", "B2", "C3" };
-     * </code>
-     * </pre>
-     *
-     * <p>
-     * Calling this method will print:
-     * </p>
-     *
-     * <pre>
-     * <code>
-     * A, B, C
-     * 1, 2, 3
-     * A1, B2, C3
-     * </code>
-     * </pre>
-     *
-     * @param values
-     *            the values to print.
-     * @throws IOException
-     *             If an I/O error occurs
-     */
-    public void printRecords(final Object... values) throws IOException {
-        printRecords(Arrays.asList(values));
-    }
-
-    /**
-     * Prints all the objects in the given JDBC result set.
-     *
-     * @param resultSet
-     *            result set the values to print.
-     * @throws IOException
-     *             If an I/O 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();
-        while (resultSet.next()) {
-            for (int i = 1; i <= columnCount; i++) {
-                final Object object = resultSet.getObject(i);
-                // TODO Who manages the Clob? The JDBC driver or must we close it? Is it driver-dependent?
-                print(object instanceof Clob ? ((Clob) object).getCharacterStream() : object);
-            }
-            println();
-        }
-    }
-
-    /**
-     * Prints all the objects with metadata in the given JDBC result set based on the header boolean.
-     *
-     * @param resultSet source of row data.
-     * @param printHeader whether to print headers.
-     * @throws IOException If an I/O error occurs
-     * @throws SQLException if a database access error occurs
-     * @since 1.9.0
-     */
-    public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException {
-        if (printHeader) {
-            printHeaders(resultSet);
-        }
-        printRecords(resultSet);
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.csv;
+
+import static org.apache.commons.csv.Constants.CR;
+import static org.apache.commons.csv.Constants.LF;
+import static org.apache.commons.csv.Constants.SP;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.sql.Clob;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * Prints values in a {@link CSVFormat CSV format}.
+ *
+ * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
+ * Values are printed according to {@link String#valueOf(Object)}.
+ * To complete a record the {@link #println()} method has to be called.
+ * Comments can be appended by calling {@link #printComment(String)}.
+ * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
+ * </p>
+ *
+ * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
+ * or {@link #printRecord(Iterable)}.
+ * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
+ * methods can be used to print several records at once.
+ * </p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>
+ * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
+ *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
+ *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
+ *     printer.println();
+ *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
+ * } catch (IOException ex) {
+ *     ex.printStackTrace();
+ * }
+ * </pre>
+ *
+ * <p>This code will write the following to csv.txt:</p>
+ * <pre>
+ * id,userName,firstName,lastName,birthday
+ * 1,john73,John,Doe,1973-09-15
+ *
+ * 2,mary,Mary,Meyer,1985-03-29
+ * </pre>
+ */
+public final class CSVPrinter implements Flushable, Closeable {
+
+    /**
+     * Throws the given throwable.
+     *
+     * @param <T> The throwable cast type.
+     * @param throwable The throwable to rethrow.
+     * @return nothing because we throw.
+     * @throws T Always thrown.
+     */
+    @SuppressWarnings("unchecked")
+    private static <T extends Throwable> RuntimeException rethrow(final Throwable throwable) throws T {
+        throw (T) throwable;
+    }
+    /** The place that the values get written. */
+    private final Appendable appendable;
+
+    private final CSVFormat format;
+
+    /** True if we just began a new record. */
+    private boolean newRecord = true;
+
+    /**
+     * Creates a printer that will print values to the given stream following the CSVFormat.
+     * <p>
+     * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
+     * and escaping with a different character) are not supported.
+     * </p>
+     *
+     * @param appendable
+     *            stream to which to print. Must not be null.
+     * @param format
+     *            the CSV format. Must not be null.
+     * @throws IOException
+     *             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.
+     */
+    public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException {
+        Objects.requireNonNull(appendable, "appendable");
+        Objects.requireNonNull(format, "format");
+
+        this.appendable = appendable;
+        this.format = format.copy();
+        // 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 (final String line : format.getHeaderComments()) {
+                this.printComment(line);
+            }
+        }
+        if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
+            this.printRecord((Object[]) format.getHeader());
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        close(false);
+    }
+
+    /**
+     * Closes the underlying stream with an optional flush first.
+     * @param flush whether to flush before the actual close.
+     *
+     * @throws IOException
+     *             If an I/O error occurs
+     * @since 1.6
+     */
+    public void close(final boolean flush) throws IOException {
+        if (flush || format.getAutoFlush()) {
+            flush();
+        }
+        if (appendable instanceof Closeable) {
+            ((Closeable) appendable).close();
+        }
+    }
+
+    /**
+     * Flushes the underlying stream.
+     *
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    @Override
+    public void flush() throws IOException {
+        if (appendable instanceof Flushable) {
+            ((Flushable) appendable).flush();
+        }
+    }
+
+    /**
+     * Gets the target Appendable.
+     *
+     * @return the target Appendable.
+     */
+    public Appendable getOut() {
+        return this.appendable;
+    }
+
+    /**
+     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
+     *
+     * @param value
+     *            value to be output.
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public synchronized void print(final Object value) throws IOException {
+        format.print(value, appendable, newRecord);
+        newRecord = false;
+    }
+
+    /**
+     * Prints a comment on a new line among the delimiter separated values.
+     *
+     * <p>
+     * Comments will always begin on a new line and occupy at least one full line. The character specified to start
+     * comments and a space will be inserted at the beginning of each new line in the comment.
+     * </p>
+     *
+     * <p>
+     * If comments are disabled in the current CSV format this method does nothing.
+     * </p>
+     *
+     * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
+     * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
+     * line breaks as record separator.</p>
+     *
+     * @param comment
+     *            the comment to output
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public synchronized void printComment(final String comment) throws IOException {
+        if (comment == null || !format.isCommentMarkerSet()) {
+            return;
+        }
+        if (!newRecord) {
+            println();
+        }
+        appendable.append(format.getCommentMarker().charValue());
+        appendable.append(SP);
+        for (int i = 0; i < comment.length(); i++) {
+            final char c = comment.charAt(i);
+            switch (c) {
+            case CR:
+                if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
+                    i++;
+                }
+                //$FALL-THROUGH$ break intentionally excluded.
+            case LF:
+                println();
+                appendable.append(format.getCommentMarker().charValue());
+                appendable.append(SP);
+                break;
+            default:
+                appendable.append(c);
+                break;
+            }
+        }
+        println();
+    }
+
+    /**
+     * Prints headers for a result set based on its metadata.
+     *
+     * @param resultSet The result set to query for metadata.
+     * @throws IOException If an I/O error occurs.
+     * @throws SQLException If a database access error occurs or this method is called on a closed result set.
+     * @since 1.9.0
+     */
+    public synchronized void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
+        printRecord((Object[]) format.builder().setHeader(resultSet).build().getHeader());
+    }
+
+    /**
+     * Outputs the record separator.
+     *
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public synchronized void println() throws IOException {
+        format.println(appendable);
+        newRecord = true;
+    }
+
+    /**
+     * Prints the given values as a single record of delimiter separated values followed by the record separator.
+     *
+     * <p>
+     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
+     * separator to the output after printing the record, so there is no need to call {@link #println()}.
+     * </p>
+     *
+     * @param values
+     *            values to output.
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public synchronized void printRecord(final Iterable<?> values) throws IOException {
+        for (final Object value : values) {
+            print(value);
+        }
+        println();
+    }
+
+    /**
+     * Prints the given values as a single record of delimiter separated values followed by the record separator.
+     *
+     * <p>
+     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
+     * separator to the output after printing the record, so there is no need to call {@link #println()}.
+     * </p>
+     *
+     * @param values
+     *            values to output.
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public void printRecord(final Object... values) throws IOException {
+        printRecord(Arrays.asList(values));
+    }
+
+    /**
+     * Prints the given values as a single record of delimiter separated values followed by the record separator.
+     *
+     * <p>
+     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
+     * separator to the output after printing the record, so there is no need to call {@link #println()}.
+     * </p>
+     *
+     * @param values
+     *            values to output.
+     * @throws IOException
+     *             If an I/O error occurs
+     * @since 1.10.0
+     */
+    public synchronized void printRecord(final Stream<?> values) throws IOException {
+        values.forEachOrdered(t -> {
+            try {
+                print(t);
+            } catch (IOException e) {
+                throw rethrow(e);
+            }
+        });
+        println();
+    }
+
+    private void printRecordObject(final Object value) throws IOException {
+        if (value instanceof Object[]) {
+            this.printRecord((Object[]) value);
+        } else if (value instanceof Iterable) {
+            this.printRecord((Iterable<?>) value);
+        } else {
+            this.printRecord(value);
+        }
+    }
+
+    /**
+     * Prints all the objects in the given {@link Iterable} handling nested collections/arrays as records.
+     *
+     * <p>
+     * If the given Iterable only contains simple objects, this method will print a single record like
+     * {@link #printRecord(Iterable)}. If the given Iterable contains nested collections/arrays those nested elements
+     * will each be printed as records using {@link #printRecord(Object...)}.
+     * </p>
+     *
+     * <p>
+     * Given the following data structure:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * List&lt;String[]&gt; data = new ArrayList&lt;&gt;();
+     * data.add(new String[]{ "A", "B", "C" });
+     * data.add(new String[]{ "1", "2", "3" });
+     * data.add(new String[]{ "A1", "B2", "C3" });
+     * </code>
+     * </pre>
+     *
+     * <p>
+     * Calling this method will print:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * A, B, C
+     * 1, 2, 3
+     * A1, B2, C3
+     * </code>
+     * </pre>
+     *
+     * @param values
+     *            the values to print.
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public void printRecords(final Iterable<?> values) throws IOException {
+        for (final Object value : values) {
+            printRecordObject(value);
+        }
+    }
+
+    /**
+     * 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
+     * {@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>
+     *
+     * <p>
+     * Given the following data structure:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * String[][] data = new String[3][]
+     * data[0] = String[]{ "A", "B", "C" };
+     * data[1] = new String[]{ "1", "2", "3" };
+     * data[2] = new String[]{ "A1", "B2", "C3" };
+     * </code>
+     * </pre>
+     *
+     * <p>
+     * Calling this method will print:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * A, B, C
+     * 1, 2, 3
+     * A1, B2, C3
+     * </code>
+     * </pre>
+     *
+     * @param values
+     *            the values to print.
+     * @throws IOException
+     *             If an I/O error occurs
+     */
+    public void printRecords(final Object... values) throws IOException {
+        printRecords(Arrays.asList(values));
+    }
+
+    /**
+     * Prints all the objects in the given JDBC result set.
+     *
+     * @param resultSet
+     *            result set the values to print.
+     * @throws IOException
+     *             If an I/O 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();
+        while (resultSet.next()) {
+            for (int i = 1; i <= columnCount; i++) {
+                final Object object = resultSet.getObject(i);
+                // TODO Who manages the Clob? The JDBC driver or must we close it? Is it driver-dependent?
+                print(object instanceof Clob ? ((Clob) object).getCharacterStream() : object);
+            }
+            println();
+        }
+    }
+
+    /**
+     * Prints all the objects with metadata in the given JDBC result set based on the header boolean.
+     *
+     * @param resultSet source of row data.
+     * @param printHeader whether to print headers.
+     * @throws IOException If an I/O error occurs
+     * @throws SQLException if a database access error occurs
+     * @since 1.9.0
+     */
+    public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException {
+        if (printHeader) {
+            printHeaders(resultSet);
+        }
+        printRecords(resultSet);
+    }
+
+    /**
+     * Prints all the objects in the given {@link Stream} handling nested collections/arrays as records.
+     *
+     * <p>
+     * If the given Stream only contains simple objects, this method will print a single record like
+     * {@link #printRecord(Iterable)}. If the given Stream contains nested collections/arrays those nested elements
+     * will each be printed as records using {@link #printRecord(Object...)}.
+     * </p>
+     *
+     * <p>
+     * Given the following data structure:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * List&lt;String[]&gt; data = new ArrayList&lt;&gt;();
+     * data.add(new String[]{ "A", "B", "C" });
+     * data.add(new String[]{ "1", "2", "3" });
+     * data.add(new String[]{ "A1", "B2", "C3" });
+     * Stream&lt;String[]&gt; stream = data.stream();
+     * </code>
+     * </pre>
+     *
+     * <p>
+     * Calling this method will print:
+     * </p>
+     *
+     * <pre>
+     * <code>
+     * A, B, C
+     * 1, 2, 3
+     * A1, B2, C3
+     * </code>
+     * </pre>
+     *
+     * @param values
+     *            the values to print.
+     * @throws IOException
+     *             If an I/O error occurs
+     * @since 1.10.0
+     */
+    @SuppressWarnings("unused") // rethrow() throws IOException
+    public void printRecords(final Stream<?> values) throws IOException {
+        values.forEachOrdered(t -> {
+            try {
+                printRecordObject(t);
+            } catch (IOException e) {
+                throw rethrow(e);
+            }
+        });
+    }
+}
diff --git a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
index 6890bc26..8a9a36bc 100644
--- a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
+++ b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java
@@ -1,1710 +1,1742 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.csv;
-
-import static org.apache.commons.csv.Constants.BACKSLASH;
-import static org.apache.commons.csv.Constants.CR;
-import static org.junit.jupiter.api.Assertions.assertArrayEquals;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import java.io.CharArrayWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.sql.BatchUpdateException;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Random;
-import java.util.Vector;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.output.NullOutputStream;
-import org.apache.commons.lang3.StringUtils;
-import org.h2.tools.SimpleResultSet;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests {@link CSVPrinter}.
- */
-public class CSVPrinterTest {
-
-    private static final char DQUOTE_CHAR = '"';
-    private static final char EURO_CH = '\u20AC';
-    private static final int ITERATIONS_FOR_RANDOM_TEST = 50000;
-    private static final char QUOTE_CH = '\'';
-
-    private static String printable(final String s) {
-        final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < s.length(); i++) {
-            final char ch = s.charAt(i);
-            if (ch <= ' ' || ch >= 128) {
-                sb.append("(").append((int) ch).append(")");
-            } else {
-                sb.append(ch);
-            }
-        }
-        return sb.toString();
-    }
-
-    private String longText2;
-
-    private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
-
-    private void doOneRandom(final CSVFormat format) throws Exception {
-        final Random r = new Random();
-
-        final int nLines = r.nextInt(4) + 1;
-        final int nCol = r.nextInt(3) + 1;
-        // nLines=1;nCol=2;
-        final String[][] lines = generateLines(nLines, nCol);
-
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-
-            for (int i = 0; i < nLines; i++) {
-                // for (int j=0; j<lines[i].length; j++) System.out.println("### VALUE=:" + printable(lines[i][j]));
-                printer.printRecord((Object[]) lines[i]);
-            }
-
-            printer.flush();
-        }
-        final String result = sw.toString();
-        // System.out.println("### :" + printable(result));
-
-        try (final CSVParser parser = CSVParser.parse(result, format)) {
-            final List<CSVRecord> parseResult = parser.getRecords();
-
-            final String[][] expected = lines.clone();
-            for (int i = 0; i < expected.length; i++) {
-                expected[i] = expectNulls(expected[i], format);
-            }
-            Utils.compare("Printer output :" + printable(result), expected, parseResult);
-        }
-    }
-
-    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 (Objects.equals(csvFormat.getNullString(), fixed[i])) {
-                fixed[i] = null;
-            }
-        }
-        return fixed;
-    }
-
-    private String[][] generateLines(final int nLines, final int nCol) {
-        final String[][] lines = new String[nLines][];
-        for (int i = 0; i < nLines; i++) {
-            final String[] line = new String[nCol];
-            lines[i] = line;
-            for (int j = 0; j < nCol; j++) {
-                line[j] = randStr();
-            }
-        }
-        return lines;
-    }
-
-    private Connection getH2Connection() throws SQLException, ClassNotFoundException {
-        Class.forName("org.h2.Driver");
-        return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", "");
-    }
-
-    private CSVPrinter printWithHeaderComments(final StringWriter sw, final Date now, final CSVFormat baseFormat)
-            throws IOException {
-        // Use withHeaderComments first to test CSV-145
-        // @formatter:off
-        final CSVFormat format = baseFormat.builder()
-                .setHeaderComments("Generated by Apache Commons CSV 1.1", now)
-                .setCommentMarker('#')
-                .setHeader("Col1", "Col2")
-                .build();
-        // @formatter:on
-        final CSVPrinter csvPrinter = format.print(sw);
-        csvPrinter.printRecord("A", "B");
-        csvPrinter.printRecord("C", "D");
-        csvPrinter.close();
-        return csvPrinter;
-    }
-
-    private String randStr() {
-        final Random r = new Random();
-
-        final int sz = r.nextInt(20);
-        // sz = r.nextInt(3);
-        final char[] buf = new char[sz];
-        for (int i = 0; i < sz; i++) {
-            // stick in special chars with greater frequency
-            final char ch;
-            final int what = r.nextInt(20);
-            switch (what) {
-            case 0:
-                ch = '\r';
-                break;
-            case 1:
-                ch = '\n';
-                break;
-            case 2:
-                ch = '\t';
-                break;
-            case 3:
-                ch = '\f';
-                break;
-            case 4:
-                ch = ' ';
-                break;
-            case 5:
-                ch = ',';
-                break;
-            case 6:
-                ch = DQUOTE_CHAR;
-                break;
-            case 7:
-                ch = '\'';
-                break;
-            case 8:
-                ch = BACKSLASH;
-                break;
-            default:
-                ch = (char) r.nextInt(300);
-                break;
-            // default: ch = 'a'; break;
-            }
-            buf[i] = ch;
-        }
-        return new String(buf);
-    }
-
-    private void setUpTable(final Connection connection) throws SQLException {
-        try (final Statement statement = connection.createStatement()) {
-            statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), TEXT CLOB)");
-            statement.execute("insert into TEST values(1, 'r1', 'long text 1')");
-            longText2 = StringUtils.repeat('a', IOUtils.DEFAULT_BUFFER_SIZE - 4);
-            longText2 += "\"\r\n\"a\"";
-            longText2 += StringUtils.repeat('a', IOUtils.DEFAULT_BUFFER_SIZE - 1);
-            statement.execute("insert into TEST values(2, 'r2', '" + longText2 + "')");
-            longText2 = longText2.replace("\"","\"\"");
-        }
-    }
-
-    @Test
-    public void testCloseBackwardCompatibility() throws IOException {
-        try (final Writer writer = mock(Writer.class)) {
-            final CSVFormat csvFormat = CSVFormat.DEFAULT;
-            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
-                // empty
-            }
-            verify(writer, never()).flush();
-            verify(writer, times(1)).close();
-        }}
-
-    @Test
-    public void testCloseWithCsvFormatAutoFlushOff() throws IOException {
-        try (final Writer writer = mock(Writer.class)) {
-            final CSVFormat csvFormat = CSVFormat.DEFAULT.withAutoFlush(false);
-            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
-                // empty
-            }
-            verify(writer, never()).flush();
-            verify(writer, times(1)).close();
-        }
-    }
-
-    @Test
-    public void testCloseWithCsvFormatAutoFlushOn() throws IOException {
-        // System.out.println("start method");
-        try (final Writer writer = mock(Writer.class)) {
-            final CSVFormat csvFormat = CSVFormat.DEFAULT.withAutoFlush(true);
-            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
-                // empty
-            }
-            verify(writer, times(1)).flush();
-            verify(writer, times(1)).close();
-        }}
-
-    @Test
-    public void testCloseWithFlushOff() throws IOException {
-        try (final Writer writer = mock(Writer.class)) {
-            final CSVFormat csvFormat = CSVFormat.DEFAULT;
-            @SuppressWarnings("resource")
-            final CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat);
-            csvPrinter.close(false);
-            verify(writer, never()).flush();
-            verify(writer, times(1)).close();
-        }
-    }
-
-    @Test
-    public void testCloseWithFlushOn() throws IOException {
-        try (final Writer writer = mock(Writer.class)) {
-            @SuppressWarnings("resource")
-            final CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT);
-            csvPrinter.close(true);
-            verify(writer, times(1)).flush();
-        }
-    }
-
-    @Test
-    public void testCRComment() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final Object value = "abc";
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
-            printer.print(value);
-            printer.printComment("This is a comment\r\non multiple lines\rthis is next comment\r");
-            assertEquals("abc" + recordSeparator + "# This is a comment" + recordSeparator + "# on multiple lines"
-                        + recordSeparator + "# this is next comment" + recordSeparator + "# " + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testCSV135() throws IOException {
-        final List<String> list = new LinkedList<>();
-        list.add("\"\"");   // ""
-        list.add("\\\\");   // \\
-        list.add("\\\"\\"); // \"\
-        //
-        // "",\\,\"\ (unchanged)
-        tryFormat(list, null, null, "\"\",\\\\,\\\"\\");
-        //
-        // """""",\\,"\""\" (quoted, and embedded DQ doubled)
-        tryFormat(list, '"',  null, "\"\"\"\"\"\",\\\\,\"\\\"\"\\\"");
-        //
-        // "",\\\\,\\"\\ (escapes escaped, not quoted)
-        tryFormat(list, null, '\\', "\"\",\\\\\\\\,\\\\\"\\\\");
-        //
-        // "\"\"","\\\\","\\\"\\" (quoted, and embedded DQ & escape escaped)
-        tryFormat(list, '"',  '\\', "\"\\\"\\\"\",\"\\\\\\\\\",\"\\\\\\\"\\\\\"");
-        //
-        // """""",\\,"\""\" (quoted, embedded DQ escaped)
-        tryFormat(list, '"',  '"',  "\"\"\"\"\"\",\\\\,\"\\\"\"\\\"");
-    }
-
-    @Test
-    public void testCSV259() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final Reader reader = new FileReader("src/test/resources/org/apache/commons/csv/CSV-259/sample.txt");
-                final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null))) {
-            printer.print(reader);
-            assertEquals("x!,y!,z", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDelimeterQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
-            printer.print("a,b,c");
-            printer.print("xyz");
-            assertEquals("'a,b,c',xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDelimeterQuoteNone() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            printer.print("a,b,c");
-            printer.print("xyz");
-            assertEquals("a!,b!,c,xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDelimeterStringQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.builder().setDelimiter("[|]").setQuote('\'').build())) {
-            printer.print("a[|]b[|]c");
-            printer.print("xyz");
-            assertEquals("'a[|]b[|]c'[|]xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDelimeterStringQuoteNone() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVFormat format = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').setQuoteMode(QuoteMode.NONE).build();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            printer.print("a[|]b[|]c");
-            printer.print("xyz");
-            printer.print("a[xy]bc[]");
-            assertEquals("a![!|!]b![!|!]c[|]xyz[|]a[xy]bc[]", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDelimiterEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testDelimiterPlain() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testDelimiterStringEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.builder().setDelimiter("|||").setEscape('!').setQuote(null).build())) {
-            printer.print("a|||b|||c");
-            printer.print("xyz");
-            assertEquals("a!|!|!|b!|!|!|c|||xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDisabledComment() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printComment("This is a comment");
-            assertEquals("", sw.toString());
-        }
-    }
-
-    @Test
-    public void testDontQuoteEuroFirstChar() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.RFC4180)) {
-            printer.printRecord(EURO_CH, "Deux");
-            assertEquals(EURO_CH + ",Deux" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testEolEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testEolPlain() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testEolQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testEscapeBackslash1() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
-            printer.print("\\");
-        }
-        assertEquals("\\", sw.toString());
-    }
-
-    @Test
-    public void testEscapeBackslash2() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
-            printer.print("\\\r");
-        }
-        assertEquals("'\\\r'", sw.toString());
-    }
-
-    @Test
-    public void testEscapeBackslash3() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
-            printer.print("X\\\r");
-        }
-        assertEquals("'X\\\r'", sw.toString());
-    }
-
-    @Test
-    public void testEscapeBackslash4() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
-            printer.print("\\\\");
-        }
-        assertEquals("\\\\", sw.toString());
-    }
-
-    @Test
-    public void testEscapeBackslash5() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
-            printer.print("\\\\");
-        }
-        assertEquals("\\\\", sw.toString());
-    }
-
-    @Test
-    public void testEscapeNull1() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
-            printer.print("\\");
-        }
-        assertEquals("\\", sw.toString());
-    }
-
-    @Test
-    public void testEscapeNull2() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
-            printer.print("\\\r");
-        }
-        assertEquals("\"\\\r\"", sw.toString());
-    }
-
-    @Test
-    public void testEscapeNull3() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
-            printer.print("X\\\r");
-        }
-        assertEquals("\"X\\\r\"", sw.toString());
-    }
-
-    @Test
-    public void testEscapeNull4() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
-            printer.print("\\\\");
-        }
-        assertEquals("\\\\", sw.toString());
-    }
-
-    @Test
-    public void testEscapeNull5() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
-            printer.print("\\\\");
-        }
-        assertEquals("\\\\", sw.toString());
-    }
-
-    @Test
-    public void testExcelPrintAllArrayOfArrays() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecords((Object[]) new String[][] { { "r1c1", "r1c2" }, { "r2c1", "r2c2" } });
-            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testExcelPrintAllArrayOfLists() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecords(
-                    (Object[]) new List[] { Arrays.asList("r1c1", "r1c2"), Arrays.asList("r2c1", "r2c2") });
-            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testExcelPrintAllIterableOfArrays() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecords(Arrays.asList(new String[][] { { "r1c1", "r1c2" }, { "r2c1", "r2c2" } }));
-            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testExcelPrintAllIterableOfLists() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecords(
-                    Arrays.asList(Arrays.asList("r1c1", "r1c2"), Arrays.asList("r2c1", "r2c2")));
-            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testExcelPrinter1() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecord("a", "b");
-            assertEquals("a,b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testExcelPrinter2() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
-            printer.printRecord("a,b", "b");
-            assertEquals("\"a,b\",b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testHeader() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testHeaderCommentExcel() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final Date now = new Date();
-        final CSVFormat format = CSVFormat.EXCEL;
-        try (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());
-        }
-    }
-
-    @Test
-    public void testHeaderCommentTdf() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final Date now = new Date();
-        final CSVFormat format = CSVFormat.TDF;
-        try (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());
-        }
-    }
-
-    @Test
-    public void testHeaderNotSet() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testInvalidFormat() {
-        assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withDelimiter(CR));
-    }
-
-    @Test
-    public void testJdbcPrinter() throws IOException, ClassNotFoundException, SQLException {
-        final StringWriter sw = new StringWriter();
-        try (final Connection connection = getH2Connection()) {
-            setUpTable(connection);
-            try (final Statement stmt = connection.createStatement();
-                    final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);
-                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");) {
-                printer.printRecords(resultSet);
-            }
-        }
-        assertEquals("1,r1,\"long text 1\"" + recordSeparator + "2,r2,\"" + longText2 + "\"" + recordSeparator, sw.toString());
-    }
-
-    @Test
-    public void testJdbcPrinterWithResultSet() throws IOException, ClassNotFoundException, SQLException {
-        final StringWriter sw = new StringWriter();
-        Class.forName("org.h2.Driver");
-        try (final Connection connection = getH2Connection()) {
-            setUpTable(connection);
-            try (final Statement stmt = connection.createStatement();
-                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");
-                    final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet).print(sw)) {
-                printer.printRecords(resultSet);
-            }
-        }
-        assertEquals("ID,NAME,TEXT" + recordSeparator + "1,r1,\"long text 1\"" + recordSeparator + "2,r2,\"" + longText2
-                + "\"" + recordSeparator, sw.toString());
-    }
-
-    @Test
-    public void testJdbcPrinterWithResultSetHeader() throws IOException, ClassNotFoundException, SQLException {
-        final StringWriter sw = new StringWriter();
-        try (final Connection connection = getH2Connection()) {
-            setUpTable(connection);
-            try (final Statement stmt = connection.createStatement();
-                final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);) {
-                try (final ResultSet resultSet = stmt.executeQuery("select ID, NAME from TEST")) {
-                    printer.printRecords(resultSet, true);
-                    assertEquals("ID,NAME" + recordSeparator + "1,r1" + recordSeparator + "2,r2" + recordSeparator,
-                        sw.toString());
-                }
-                try (final ResultSet resultSet = stmt.executeQuery("select ID, NAME from TEST")) {
-                    printer.printRecords(resultSet, false);
-                    assertNotEquals("ID,NAME" + recordSeparator + "1,r1" + recordSeparator + "2,r2" + recordSeparator,
-                        sw.toString());
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testJdbcPrinterWithResultSetMetaData() throws IOException, ClassNotFoundException, SQLException {
-        final StringWriter sw = new StringWriter();
-        Class.forName("org.h2.Driver");
-        try (final Connection connection = getH2Connection()) {
-            setUpTable(connection);
-            try (final Statement stmt = connection.createStatement();
-                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");
-                    final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet.getMetaData()).print(sw)) {
-                printer.printRecords(resultSet);
-                assertEquals("ID,NAME,TEXT" + recordSeparator + "1,r1,\"long text 1\"" + recordSeparator + "2,r2,\""
-                        + longText2 + "\"" + recordSeparator, sw.toString());
-            }
-        }
-    }
-
-    @Test
-    @Disabled
-    public void testJira135_part1() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
-        final StringWriter sw = new StringWriter();
-        final List<String> list = new LinkedList<>();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            list.add("\"");
-            printer.printRecord(list);
-        }
-        final String expected = "\"\\\"\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
-    }
-
-    @Test
-    @Disabled
-    public void testJira135_part2() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
-        final StringWriter sw = new StringWriter();
-        final List<String> list = new LinkedList<>();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            list.add("\n");
-            printer.printRecord(list);
-        }
-        final String expected = "\"\\n\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
-    }
-
-    @Test
-    @Disabled
-    public void testJira135_part3() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
-        final StringWriter sw = new StringWriter();
-        final List<String> list = new LinkedList<>();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            list.add("\\");
-            printer.printRecord(list);
-        }
-        final String expected = "\"\\\\\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
-    }
-
-    @Test
-    @Disabled
-    public void testJira135All() throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
-        final StringWriter sw = new StringWriter();
-        final List<String> list = new LinkedList<>();
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            list.add("\"");
-            list.add("\n");
-            list.add("\\");
-            printer.printRecord(list);
-        }
-        final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
-        assertEquals(expected, sw.toString());
-        final String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(list.toArray(), format), record0);
-    }
-
-    @Test
-    public void testMongoDbCsvBasic() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
-            printer.printRecord("a", "b");
-            assertEquals("a,b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbCsvCommaInValue() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
-            printer.printRecord("a,b", "c");
-            assertEquals("\"a,b\",c" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbCsvDoubleQuoteInValue() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
-            printer.printRecord("a \"c\" b", "d");
-            assertEquals("\"a \"\"c\"\" b\",d" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbCsvTabInValue() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
-            printer.printRecord("a\tb", "c");
-            assertEquals("a\tb,c" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbTsvBasic() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
-            printer.printRecord("a", "b");
-            assertEquals("a\tb" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbTsvCommaInValue() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
-            printer.printRecord("a,b", "c");
-            assertEquals("a,b\tc" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMongoDbTsvTabInValue() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
-            printer.printRecord("a\tb", "c");
-            assertEquals("\"a\tb\"\tc" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testMultiLineComment() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
-            printer.printComment("This is a comment\non multiple lines");
-
-            assertEquals("# This is a comment" + recordSeparator + "# on multiple lines" + recordSeparator,
-                    sw.toString());
-        }
-    }
-
-    @Test
-    public void testMySqlNullOutput() throws IOException {
-        Object[] s = new String[] { "NULL", null };
-        CSVFormat format = CSVFormat.MYSQL.withQuote(DQUOTE_CHAR).withNullString("NULL")
-            .withQuoteMode(QuoteMode.NON_NUMERIC);
-        StringWriter writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        String expected = "\"NULL\"\tNULL\n";
-        assertEquals(expected, writer.toString());
-        String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(s, record0);
-
-        s = new String[] { "\\N", null };
-        format = CSVFormat.MYSQL.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "A" };
-        format = CSVFormat.MYSQL.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\n", "A" };
-        format = CSVFormat.MYSQL.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\n\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.MYSQL.withNullString("NULL");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\tNULL\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.MYSQL;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "", "\u000e,\\\r" };
-        format = CSVFormat.MYSQL;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "NULL", "\\\r" };
-        format = CSVFormat.MYSQL;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "NULL\t\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\\r" };
-        format = CSVFormat.MYSQL;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-    }
-
-    @Test
-    public void testMySqlNullStringDefault() {
-        assertEquals("\\N", CSVFormat.MYSQL.getNullString());
-    }
-
-    @Test
-    public void testNewCsvPrinterAppendableNullFormat() {
-        assertThrows(NullPointerException.class, () -> new CSVPrinter(new StringWriter(), null));
-    }
-
-    @Test
-    public void testNewCsvPrinterNullAppendableFormat() {
-        assertThrows(NullPointerException.class, () -> new CSVPrinter(null, CSVFormat.DEFAULT));
-    }
-
-    @Test
-    public void testNotFlushable() throws IOException {
-        final Appendable out = new StringBuilder();
-        try (final CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b", "c");
-            assertEquals("a,b,c" + recordSeparator, out.toString());
-            printer.flush();
-        }
-    }
-
-    @Test
-    public void testParseCustomNullValues() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
-        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
-            printer.printRecord("a", null, "b");
-        }
-        final String csvString = sw.toString();
-        assertEquals("a,NULL,b" + recordSeparator, csvString);
-        try (final CSVParser iterable = format.parse(new StringReader(csvString))) {
-            final Iterator<CSVRecord> iterator = iterable.iterator();
-            final CSVRecord record = iterator.next();
-            assertEquals("a", record.get(0));
-            assertNull(record.get(1));
-            assertEquals("b", record.get(2));
-            assertFalse(iterator.hasNext());
-        }
-    }
-
-    @Test
-    public void testPlainEscaped() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'))) {
-            printer.print("abc");
-            printer.print("xyz");
-            assertEquals("abc,xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testPlainPlain() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null))) {
-            printer.print("abc");
-            printer.print("xyz");
-            assertEquals("abc,xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testPlainQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
-            printer.print("abc");
-            assertEquals("abc", sw.toString());
-        }
-    }
-
-    @Test
-    @Disabled
-    public void testPostgreSqlCsvNullOutput() throws IOException {
-        Object[] s = new String[] { "NULL", null };
-        CSVFormat format = CSVFormat.POSTGRESQL_CSV.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL);
-        StringWriter writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        String expected = "\"NULL\",NULL\n";
-        assertEquals(expected, writer.toString());
-        String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(new Object[2], record0);
-
-        s = new String[] { "\\N", null };
-        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "A" };
-        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\n", "A" };
-        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\n\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.POSTGRESQL_CSV.withNullString("NULL");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\tNULL\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.POSTGRESQL_CSV;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "", "\u000e,\\\r" };
-        format = CSVFormat.POSTGRESQL_CSV;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "NULL", "\\\r" };
-        format = CSVFormat.POSTGRESQL_CSV;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "NULL\t\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\\r" };
-        format = CSVFormat.POSTGRESQL_CSV;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-    }
-
-    @Test
-    @Disabled
-    public void testPostgreSqlCsvTextOutput() throws IOException {
-        Object[] s = new String[] { "NULL", null };
-        CSVFormat format = CSVFormat.POSTGRESQL_TEXT.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL);
-        StringWriter writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        String expected = "\"NULL\"\tNULL\n";
-        assertEquals(expected, writer.toString());
-        String[] record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(new Object[2], record0);
-
-        s = new String[] { "\\N", null };
-        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "A" };
-        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\n", "A" };
-        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\n\tA\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.POSTGRESQL_TEXT.withNullString("NULL");
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\tNULL\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "", null };
-        format = CSVFormat.POSTGRESQL_TEXT;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\t\\N\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\N", "", "\u000e,\\\r" };
-        format = CSVFormat.POSTGRESQL_TEXT;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "NULL", "\\\r" };
-        format = CSVFormat.POSTGRESQL_TEXT;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "NULL\t\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-
-        s = new String[] { "\\\r" };
-        format = CSVFormat.POSTGRESQL_TEXT;
-        writer = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
-            printer.printRecord(s);
-        }
-        expected = "\\\\\\r\n";
-        assertEquals(expected, writer.toString());
-        record0 = toFirstRecordValues(expected, format);
-        assertArrayEquals(expectNulls(s, format), record0);
-    }
-
-    @Test
-    public void testPostgreSqlNullStringDefaultCsv() {
-        assertEquals("", CSVFormat.POSTGRESQL_CSV.getNullString());
-    }
-
-    @Test
-    public void testPostgreSqlNullStringDefaultText() {
-        assertEquals("\\N", CSVFormat.POSTGRESQL_TEXT.getNullString());
-    }
-
-    @Test
-    public void testPrint() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(sw)) {
-            printer.printRecord("a", "b\\c");
-            assertEquals("a,b\\c" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrintCSVParser() throws IOException {
-        final String code = "a1,b1\n" // 1)
-            + "a2,b2\n" // 2)
-            + "a3,b3\n" // 3)
-            + "a4,b4\n"// 4)
-        ;
-        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
-        final CSVFormat format = CSVFormat.DEFAULT;
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
-            printer.printRecords(parser);
-        }
-        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
-            final List<CSVRecord> records = parser.getRecords();
-            assertFalse(records.isEmpty());
-            Utils.compare("Fail", res, records);
-        }
-    }
-
-    @Test
-    public void testPrintCSVRecord() throws IOException {
-        final String code = "a1,b1\n" // 1)
-            + "a2,b2\n" // 2)
-            + "a3,b3\n" // 3)
-            + "a4,b4\n"// 4)
-        ;
-        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
-        final CSVFormat format = CSVFormat.DEFAULT;
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
-            for (final CSVRecord record : parser) {
-                printer.printRecord(record);
-            }
-        }
-        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
-            final List<CSVRecord> records = parser.getRecords();
-            assertFalse(records.isEmpty());
-            Utils.compare("Fail", res, records);
-        }
-    }
-
-    @Test
-    public void testPrintCSVRecords() throws IOException {
-        final String code = "a1,b1\n" // 1)
-            + "a2,b2\n" // 2)
-            + "a3,b3\n" // 3)
-            + "a4,b4\n"// 4)
-        ;
-        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
-        final CSVFormat format = CSVFormat.DEFAULT;
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
-            printer.printRecords(parser.getRecords());
-        }
-        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
-            final List<CSVRecord> records = parser.getRecords();
-            assertFalse(records.isEmpty());
-            Utils.compare("Fail", res, records);
-        }
-    }
-
-    @Test
-    public void testPrintCustomNullValues() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"))) {
-            printer.printRecord("a", null, "b");
-            assertEquals("a,NULL,b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter1() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b");
-            assertEquals("a,b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter2() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a,b", "b");
-            assertEquals("\"a,b\",b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter3() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a, b", "b ");
-            assertEquals("\"a, b\",\"b \"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter4() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b\"c");
-            assertEquals("a,\"b\"\"c\"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter5() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b\nc");
-            assertEquals("a,\"b\nc\"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter6() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b\r\nc");
-            assertEquals("a,\"b\r\nc\"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrinter7() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", "b\\c");
-            assertEquals("a,b\\c" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrintNullValues() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
-            printer.printRecord("a", null, "b");
-            assertEquals("a,,b" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testPrintOnePositiveInteger() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.MINIMAL))) {
-            printer.print(Integer.MAX_VALUE);
-            assertEquals(String.valueOf(Integer.MAX_VALUE), sw.toString());
-        }
-    }
-
-    /**
-     * Test to target the use of {@link IOUtils#copy(java.io.Reader, Appendable)} which directly
-     * buffers the value from the Reader to the Appendable.
-     *
-     * <p>Requires the format to have no quote or escape character, value to be a
-     * {@link java.io.Reader Reader} and the output <i>MUST NOT</i> be a
-     * {@link java.io.Writer Writer} but some other Appendable.</p>
-     *
-     * @throws IOException Not expected to happen
-     */
-    @Test
-    public void testPrintReaderWithoutQuoteToAppendable() throws IOException {
-        final StringBuilder sb = new StringBuilder();
-        final String content = "testValue";
-        try (final CSVPrinter printer = new CSVPrinter(sb, CSVFormat.DEFAULT.withQuote(null))) {
-            final StringReader value = new StringReader(content);
-            printer.print(value);
-        }
-        assertEquals(content, sb.toString());
-    }
-
-    /**
-     * Test to target the use of {@link IOUtils#copyLarge(java.io.Reader, Writer)} which directly
-     * buffers the value from the Reader to the Writer.
-     *
-     * <p>Requires the format to have no quote or escape character, value to be a
-     * {@link java.io.Reader Reader} and the output <i>MUST</i> be a
-     * {@link java.io.Writer Writer}.</p>
-     *
-     * @throws IOException Not expected to happen
-     */
-    @Test
-    public void testPrintReaderWithoutQuoteToWriter() throws IOException {
-        final StringWriter sw = new StringWriter();
-        final String content = "testValue";
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null))) {
-            final StringReader value = new StringReader(content);
-            printer.print(value);
-        }
-        assertEquals(content, sw.toString());
-    }
-
-    @Test
-    public void testPrintRecordsWithCSVRecord() throws IOException {
-        final String[] values = {"A", "B", "C"};
-        final String rowData = StringUtils.join(values, ',');
-        final CharArrayWriter charArrayWriter = new CharArrayWriter(0);
-        try (final CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(rowData));
-            final CSVPrinter csvPrinter = CSVFormat.INFORMIX_UNLOAD.print(charArrayWriter)) {
-            for (final CSVRecord record : parser) {
-                csvPrinter.printRecord(record);
-            }
-        }
-        assertEquals(6, charArrayWriter.size());
-        assertEquals("A|B|C" + CSVFormat.INFORMIX_UNLOAD.getRecordSeparator(), charArrayWriter.toString());
-    }
-
-    @Test
-    public void testPrintRecordsWithEmptyVector() throws IOException {
-        final PrintStream out = System.out;
-        try {
-            System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
-            try (CSVPrinter csvPrinter = CSVFormat.POSTGRESQL_TEXT.printer()) {
-                final Vector<CSVFormatTest.EmptyEnum> vector = new Vector<>();
-                final int expectedCapacity = 23;
-                vector.setSize(expectedCapacity);
-                csvPrinter.printRecords(vector);
-                assertEquals(expectedCapacity, vector.capacity());
-            }
-        } finally {
-            System.setOut(out);
-        }
-    }
-
-    @Test
-    public void testPrintRecordsWithObjectArray() throws IOException {
-        final CharArrayWriter charArrayWriter = new CharArrayWriter(0);
-        try (CSVPrinter csvPrinter = CSVFormat.INFORMIX_UNLOAD.print(charArrayWriter)) {
-            final HashSet<BatchUpdateException> hashSet = new HashSet<>();
-            final Object[] objectArray = new Object[6];
-            objectArray[3] = hashSet;
-            csvPrinter.printRecords(objectArray);
-        }
-        assertEquals(6, charArrayWriter.size());
-        assertEquals("\n\n\n\n\n\n", charArrayWriter.toString());
-    }
-
-    @Test
-    public void testPrintRecordsWithResultSetOneRow() throws IOException, SQLException {
-        try (CSVPrinter csvPrinter = CSVFormat.MYSQL.printer()) {
-            try (ResultSet resultSet = new SimpleResultSet()) {
-                csvPrinter.printRecords(resultSet);
-                assertEquals(0, resultSet.getRow());
-            }
-        }
-    }
-
-    @Test
-    public void testPrintToFileWithCharsetUtf16Be() throws IOException {
-        final File file = File.createTempFile(getClass().getName(), ".csv");
-        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file, StandardCharsets.UTF_16BE)) {
-            printer.printRecord("a", "b\\c");
-        }
-        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, StandardCharsets.UTF_16BE));
-    }
-
-    @Test
-    public void testPrintToFileWithDefaultCharset() throws IOException {
-        final File file = File.createTempFile(getClass().getName(), ".csv");
-        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file, Charset.defaultCharset())) {
-            printer.printRecord("a", "b\\c");
-        }
-        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, Charset.defaultCharset()));
-    }
-
-    @Test
-    public void testPrintToPathWithDefaultCharset() throws IOException {
-        final File file = File.createTempFile(getClass().getName(), ".csv");
-        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file.toPath(), Charset.defaultCharset())) {
-            printer.printRecord("a", "b\\c");
-        }
-        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, Charset.defaultCharset()));
-    }
-
-    @Test
-    public void testQuoteAll() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.ALL))) {
-            printer.printRecord("a", "b\nc", "d");
-            assertEquals("\"a\",\"b\nc\",\"d\"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testQuoteCommaFirstChar() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.RFC4180)) {
-            printer.printRecord(",");
-            assertEquals("\",\"" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testQuoteNonNumeric() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.NON_NUMERIC))) {
-            printer.printRecord("a", "b\nc", Integer.valueOf(1));
-            assertEquals("\"a\",\"b\nc\",1" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testRandomDefault() throws Exception {
-        doRandom(CSVFormat.DEFAULT, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    public void testRandomExcel() throws Exception {
-        doRandom(CSVFormat.EXCEL, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    @Disabled
-    public void testRandomMongoDbCsv() throws Exception {
-        doRandom(CSVFormat.MONGODB_CSV, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    public void testRandomMySql() throws Exception {
-        doRandom(CSVFormat.MYSQL, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    @Disabled
-    public void testRandomOracle() throws Exception {
-        doRandom(CSVFormat.ORACLE, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    @Disabled
-    public void testRandomPostgreSqlCsv() throws Exception {
-        doRandom(CSVFormat.POSTGRESQL_CSV, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    @Disabled
-    public void testRandomPostgreSqlText() throws Exception {
-        doRandom(CSVFormat.POSTGRESQL_TEXT, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-
-    @Test
-    public void testRandomRfc4180() throws Exception {
-        doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    public void testRandomTdf() throws Exception {
-        doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
-    }
-
-    @Test
-    public void testSingleLineComment() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
-            printer.printComment("This is a comment");
-            assertEquals("# This is a comment" + recordSeparator, sw.toString());
-        }
-    }
-
-    @Test
-    public void testSingleQuoteQuoted() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
-            printer.print("a'b'c");
-            printer.print("xyz");
-            assertEquals("'a''b''c',xyz", sw.toString());
-        }
-    }
-
-    @Test
-    public void testSkipHeaderRecordFalse() throws IOException {
-        // functionally identical to testHeader, used to test CSV-153
-        final StringWriter sw = new StringWriter();
-        try (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());
-        }
-    }
-
-    @Test
-    public void testSkipHeaderRecordTrue() throws IOException {
-        // functionally identical to testHeaderNotSet, used to test CSV-153
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw,
-                CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(true))) {
-            printer.printRecord("a", "b", "c");
-            printer.printRecord("x", "y", "z");
-            assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
-        }
-    }
-
-    @Test
-    public void testTrailingDelimiterOnTwoColumns() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrailingDelimiter())) {
-            printer.printRecord("A", "B");
-            assertEquals("A,B,\r\n", sw.toString());
-        }
-    }
-
-    @Test
-    public void testTrimOffOneColumn() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim(false))) {
-            printer.print(" A ");
-            assertEquals("\" A \"", sw.toString());
-        }
-    }
-
-    @Test
-    public void testTrimOnOneColumn() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim())) {
-            printer.print(" A ");
-            assertEquals("A", sw.toString());
-        }
-    }
-
-    @Test
-    public void testTrimOnTwoColumns() throws IOException {
-        final StringWriter sw = new StringWriter();
-        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim())) {
-            printer.print(" A ");
-            printer.print(" B ");
-            assertEquals("A,B", sw.toString());
-        }
-    }
-
-    private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException {
-        return CSVParser.parse(expected, format).getRecords().get(0).values();
-    }
-
-    private void tryFormat(final List<String> list, final Character quote, final Character escape, final String expected) throws IOException {
-        final CSVFormat format = CSVFormat.DEFAULT.withQuote(quote).withEscape(escape).withRecordSeparator(null);
-        final Appendable out = new StringBuilder();
-        try (final CSVPrinter printer = new CSVPrinter(out, format)) {
-            printer.printRecord(list);
-        }
-        assertEquals(expected, out.toString());
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.csv;
+
+import static org.apache.commons.csv.Constants.BACKSLASH;
+import static org.apache.commons.csv.Constants.CR;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.sql.BatchUpdateException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Vector;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.output.NullOutputStream;
+import org.apache.commons.lang3.StringUtils;
+import org.h2.tools.SimpleResultSet;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link CSVPrinter}.
+ */
+public class CSVPrinterTest {
+
+    private static final char DQUOTE_CHAR = '"';
+    private static final char EURO_CH = '\u20AC';
+    private static final int ITERATIONS_FOR_RANDOM_TEST = 50000;
+    private static final char QUOTE_CH = '\'';
+
+    private static String printable(final String s) {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            final char ch = s.charAt(i);
+            if (ch <= ' ' || ch >= 128) {
+                sb.append("(").append((int) ch).append(")");
+            } else {
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    private String longText2;
+
+    private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
+
+    private void doOneRandom(final CSVFormat format) throws Exception {
+        final Random r = new Random();
+
+        final int nLines = r.nextInt(4) + 1;
+        final int nCol = r.nextInt(3) + 1;
+        // nLines=1;nCol=2;
+        final String[][] lines = generateLines(nLines, nCol);
+
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+
+            for (int i = 0; i < nLines; i++) {
+                // for (int j=0; j<lines[i].length; j++) System.out.println("### VALUE=:" + printable(lines[i][j]));
+                printer.printRecord((Object[]) lines[i]);
+            }
+
+            printer.flush();
+        }
+        final String result = sw.toString();
+        // System.out.println("### :" + printable(result));
+
+        try (final CSVParser parser = CSVParser.parse(result, format)) {
+            final List<CSVRecord> parseResult = parser.getRecords();
+
+            final String[][] expected = lines.clone();
+            for (int i = 0; i < expected.length; i++) {
+                expected[i] = expectNulls(expected[i], format);
+            }
+            Utils.compare("Printer output :" + printable(result), expected, parseResult);
+        }
+    }
+
+    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 (Objects.equals(csvFormat.getNullString(), fixed[i])) {
+                fixed[i] = null;
+            }
+        }
+        return fixed;
+    }
+
+    private String[][] generateLines(final int nLines, final int nCol) {
+        final String[][] lines = new String[nLines][];
+        for (int i = 0; i < nLines; i++) {
+            final String[] line = new String[nCol];
+            lines[i] = line;
+            for (int j = 0; j < nCol; j++) {
+                line[j] = randStr();
+            }
+        }
+        return lines;
+    }
+
+    private Connection getH2Connection() throws SQLException, ClassNotFoundException {
+        Class.forName("org.h2.Driver");
+        return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", "");
+    }
+
+    private CSVPrinter printWithHeaderComments(final StringWriter sw, final Date now, final CSVFormat baseFormat)
+            throws IOException {
+        // Use withHeaderComments first to test CSV-145
+        // @formatter:off
+        final CSVFormat format = baseFormat.builder()
+                .setHeaderComments("Generated by Apache Commons CSV 1.1", now)
+                .setCommentMarker('#')
+                .setHeader("Col1", "Col2")
+                .build();
+        // @formatter:on
+        final CSVPrinter csvPrinter = format.print(sw);
+        csvPrinter.printRecord("A", "B");
+        csvPrinter.printRecord("C", "D");
+        csvPrinter.close();
+        return csvPrinter;
+    }
+
+    private String randStr() {
+        final Random r = new Random();
+
+        final int sz = r.nextInt(20);
+        // sz = r.nextInt(3);
+        final char[] buf = new char[sz];
+        for (int i = 0; i < sz; i++) {
+            // stick in special chars with greater frequency
+            final char ch;
+            final int what = r.nextInt(20);
+            switch (what) {
+            case 0:
+                ch = '\r';
+                break;
+            case 1:
+                ch = '\n';
+                break;
+            case 2:
+                ch = '\t';
+                break;
+            case 3:
+                ch = '\f';
+                break;
+            case 4:
+                ch = ' ';
+                break;
+            case 5:
+                ch = ',';
+                break;
+            case 6:
+                ch = DQUOTE_CHAR;
+                break;
+            case 7:
+                ch = '\'';
+                break;
+            case 8:
+                ch = BACKSLASH;
+                break;
+            default:
+                ch = (char) r.nextInt(300);
+                break;
+            // default: ch = 'a'; break;
+            }
+            buf[i] = ch;
+        }
+        return new String(buf);
+    }
+
+    private void setUpTable(final Connection connection) throws SQLException {
+        try (final Statement statement = connection.createStatement()) {
+            statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), TEXT CLOB)");
+            statement.execute("insert into TEST values(1, 'r1', 'long text 1')");
+            longText2 = StringUtils.repeat('a', IOUtils.DEFAULT_BUFFER_SIZE - 4);
+            longText2 += "\"\r\n\"a\"";
+            longText2 += StringUtils.repeat('a', IOUtils.DEFAULT_BUFFER_SIZE - 1);
+            statement.execute("insert into TEST values(2, 'r2', '" + longText2 + "')");
+            longText2 = longText2.replace("\"","\"\"");
+        }
+    }
+
+    @Test
+    public void testCloseBackwardCompatibility() throws IOException {
+        try (final Writer writer = mock(Writer.class)) {
+            final CSVFormat csvFormat = CSVFormat.DEFAULT;
+            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
+                // empty
+            }
+            verify(writer, never()).flush();
+            verify(writer, times(1)).close();
+        }}
+
+    @Test
+    public void testCloseWithCsvFormatAutoFlushOff() throws IOException {
+        try (final Writer writer = mock(Writer.class)) {
+            final CSVFormat csvFormat = CSVFormat.DEFAULT.withAutoFlush(false);
+            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
+                // empty
+            }
+            verify(writer, never()).flush();
+            verify(writer, times(1)).close();
+        }
+    }
+
+    @Test
+    public void testCloseWithCsvFormatAutoFlushOn() throws IOException {
+        // System.out.println("start method");
+        try (final Writer writer = mock(Writer.class)) {
+            final CSVFormat csvFormat = CSVFormat.DEFAULT.withAutoFlush(true);
+            try (CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat)) {
+                // empty
+            }
+            verify(writer, times(1)).flush();
+            verify(writer, times(1)).close();
+        }}
+
+    @Test
+    public void testCloseWithFlushOff() throws IOException {
+        try (final Writer writer = mock(Writer.class)) {
+            final CSVFormat csvFormat = CSVFormat.DEFAULT;
+            @SuppressWarnings("resource")
+            final CSVPrinter csvPrinter = new CSVPrinter(writer, csvFormat);
+            csvPrinter.close(false);
+            verify(writer, never()).flush();
+            verify(writer, times(1)).close();
+        }
+    }
+
+    @Test
+    public void testCloseWithFlushOn() throws IOException {
+        try (final Writer writer = mock(Writer.class)) {
+            @SuppressWarnings("resource")
+            final CSVPrinter csvPrinter = new CSVPrinter(writer, CSVFormat.DEFAULT);
+            csvPrinter.close(true);
+            verify(writer, times(1)).flush();
+        }
+    }
+
+    @Test
+    public void testCRComment() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final Object value = "abc";
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
+            printer.print(value);
+            printer.printComment("This is a comment\r\non multiple lines\rthis is next comment\r");
+            assertEquals("abc" + recordSeparator + "# This is a comment" + recordSeparator + "# on multiple lines"
+                        + recordSeparator + "# this is next comment" + recordSeparator + "# " + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testCSV135() throws IOException {
+        final List<String> list = new LinkedList<>();
+        list.add("\"\"");   // ""
+        list.add("\\\\");   // \\
+        list.add("\\\"\\"); // \"\
+        //
+        // "",\\,\"\ (unchanged)
+        tryFormat(list, null, null, "\"\",\\\\,\\\"\\");
+        //
+        // """""",\\,"\""\" (quoted, and embedded DQ doubled)
+        tryFormat(list, '"',  null, "\"\"\"\"\"\",\\\\,\"\\\"\"\\\"");
+        //
+        // "",\\\\,\\"\\ (escapes escaped, not quoted)
+        tryFormat(list, null, '\\', "\"\",\\\\\\\\,\\\\\"\\\\");
+        //
+        // "\"\"","\\\\","\\\"\\" (quoted, and embedded DQ & escape escaped)
+        tryFormat(list, '"',  '\\', "\"\\\"\\\"\",\"\\\\\\\\\",\"\\\\\\\"\\\\\"");
+        //
+        // """""",\\,"\""\" (quoted, embedded DQ escaped)
+        tryFormat(list, '"',  '"',  "\"\"\"\"\"\",\\\\,\"\\\"\"\\\"");
+    }
+
+    @Test
+    public void testCSV259() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final Reader reader = new FileReader("src/test/resources/org/apache/commons/csv/CSV-259/sample.txt");
+                final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null))) {
+            printer.print(reader);
+            assertEquals("x!,y!,z", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDelimeterQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
+            printer.print("a,b,c");
+            printer.print("xyz");
+            assertEquals("'a,b,c',xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDelimeterQuoteNone() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            printer.print("a,b,c");
+            printer.print("xyz");
+            assertEquals("a!,b!,c,xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDelimeterStringQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.builder().setDelimiter("[|]").setQuote('\'').build())) {
+            printer.print("a[|]b[|]c");
+            printer.print("xyz");
+            assertEquals("'a[|]b[|]c'[|]xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDelimeterStringQuoteNone() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVFormat format = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').setQuoteMode(QuoteMode.NONE).build();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            printer.print("a[|]b[|]c");
+            printer.print("xyz");
+            printer.print("a[xy]bc[]");
+            assertEquals("a![!|!]b![!|!]c[|]xyz[|]a[xy]bc[]", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDelimiterEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testDelimiterPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testDelimiterStringEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.builder().setDelimiter("|||").setEscape('!').setQuote(null).build())) {
+            printer.print("a|||b|||c");
+            printer.print("xyz");
+            assertEquals("a!|!|!|b!|!|!|c|||xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDisabledComment() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printComment("This is a comment");
+            assertEquals("", sw.toString());
+        }
+    }
+
+    @Test
+    public void testDontQuoteEuroFirstChar() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.RFC4180)) {
+            printer.printRecord(EURO_CH, "Deux");
+            assertEquals(EURO_CH + ",Deux" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testEolEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testEolPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testEolQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testEscapeBackslash1() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
+            printer.print("\\");
+        }
+        assertEquals("\\", sw.toString());
+    }
+
+    @Test
+    public void testEscapeBackslash2() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
+            printer.print("\\\r");
+        }
+        assertEquals("'\\\r'", sw.toString());
+    }
+
+    @Test
+    public void testEscapeBackslash3() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
+            printer.print("X\\\r");
+        }
+        assertEquals("'X\\\r'", sw.toString());
+    }
+
+    @Test
+    public void testEscapeBackslash4() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
+            printer.print("\\\\");
+        }
+        assertEquals("\\\\", sw.toString());
+    }
+
+    @Test
+    public void testEscapeBackslash5() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(QUOTE_CH))) {
+            printer.print("\\\\");
+        }
+        assertEquals("\\\\", sw.toString());
+    }
+
+    @Test
+    public void testEscapeNull1() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
+            printer.print("\\");
+        }
+        assertEquals("\\", sw.toString());
+    }
+
+    @Test
+    public void testEscapeNull2() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
+            printer.print("\\\r");
+        }
+        assertEquals("\"\\\r\"", sw.toString());
+    }
+
+    @Test
+    public void testEscapeNull3() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
+            printer.print("X\\\r");
+        }
+        assertEquals("\"X\\\r\"", sw.toString());
+    }
+
+    @Test
+    public void testEscapeNull4() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
+            printer.print("\\\\");
+        }
+        assertEquals("\\\\", sw.toString());
+    }
+
+    @Test
+    public void testEscapeNull5() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape(null))) {
+            printer.print("\\\\");
+        }
+        assertEquals("\\\\", sw.toString());
+    }
+
+    @Test
+    public void testExcelPrintAllArrayOfArrays() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecords((Object[]) new String[][] { { "r1c1", "r1c2" }, { "r2c1", "r2c2" } });
+            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrintAllArrayOfLists() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecords(
+                    (Object[]) new List[] { Arrays.asList("r1c1", "r1c2"), Arrays.asList("r2c1", "r2c2") });
+            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrintAllIterableOfArrays() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecords(Arrays.asList(new String[][] { { "r1c1", "r1c2" }, { "r2c1", "r2c2" } }));
+            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrintAllIterableOfLists() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecords(
+                    Arrays.asList(Arrays.asList("r1c1", "r1c2"), Arrays.asList("r2c1", "r2c2")));
+            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrintAllStreamOfArrays() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecords(Stream.of(new String[][] { { "r1c1", "r1c2" }, { "r2c1", "r2c2" } }));
+            assertEquals("r1c1,r1c2" + recordSeparator + "r2c1,r2c2" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrinter1() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecord("a", "b");
+            assertEquals("a,b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testExcelPrinter2() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
+            printer.printRecord("a,b", "b");
+            assertEquals("\"a,b\",b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testHeader() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testHeaderCommentExcel() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final Date now = new Date();
+        final CSVFormat format = CSVFormat.EXCEL;
+        try (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());
+        }
+    }
+
+    @Test
+    public void testHeaderCommentTdf() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final Date now = new Date();
+        final CSVFormat format = CSVFormat.TDF;
+        try (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());
+        }
+    }
+
+    @Test
+    public void testHeaderNotSet() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testInvalidFormat() {
+        assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withDelimiter(CR));
+    }
+
+    @Test
+    public void testJdbcPrinter() throws IOException, ClassNotFoundException, SQLException {
+        final StringWriter sw = new StringWriter();
+        try (final Connection connection = getH2Connection()) {
+            setUpTable(connection);
+            try (final Statement stmt = connection.createStatement();
+                    final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);
+                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");) {
+                printer.printRecords(resultSet);
+            }
+        }
+        assertEquals("1,r1,\"long text 1\"" + recordSeparator + "2,r2,\"" + longText2 + "\"" + recordSeparator, sw.toString());
+    }
+
+    @Test
+    public void testJdbcPrinterWithResultSet() throws IOException, ClassNotFoundException, SQLException {
+        final StringWriter sw = new StringWriter();
+        Class.forName("org.h2.Driver");
+        try (final Connection connection = getH2Connection()) {
+            setUpTable(connection);
+            try (final Statement stmt = connection.createStatement();
+                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");
+                    final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet).print(sw)) {
+                printer.printRecords(resultSet);
+            }
+        }
+        assertEquals("ID,NAME,TEXT" + recordSeparator + "1,r1,\"long text 1\"" + recordSeparator + "2,r2,\"" + longText2
+                + "\"" + recordSeparator, sw.toString());
+    }
+
+    @Test
+    public void testJdbcPrinterWithResultSetHeader() throws IOException, ClassNotFoundException, SQLException {
+        final StringWriter sw = new StringWriter();
+        try (final Connection connection = getH2Connection()) {
+            setUpTable(connection);
+            try (final Statement stmt = connection.createStatement();
+                final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);) {
+                try (final ResultSet resultSet = stmt.executeQuery("select ID, NAME from TEST")) {
+                    printer.printRecords(resultSet, true);
+                    assertEquals("ID,NAME" + recordSeparator + "1,r1" + recordSeparator + "2,r2" + recordSeparator,
+                        sw.toString());
+                }
+                try (final ResultSet resultSet = stmt.executeQuery("select ID, NAME from TEST")) {
+                    printer.printRecords(resultSet, false);
+                    assertNotEquals("ID,NAME" + recordSeparator + "1,r1" + recordSeparator + "2,r2" + recordSeparator,
+                        sw.toString());
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testJdbcPrinterWithResultSetMetaData() throws IOException, ClassNotFoundException, SQLException {
+        final StringWriter sw = new StringWriter();
+        Class.forName("org.h2.Driver");
+        try (final Connection connection = getH2Connection()) {
+            setUpTable(connection);
+            try (final Statement stmt = connection.createStatement();
+                    final ResultSet resultSet = stmt.executeQuery("select ID, NAME, TEXT from TEST");
+                    final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet.getMetaData()).print(sw)) {
+                printer.printRecords(resultSet);
+                assertEquals("ID,NAME,TEXT" + recordSeparator + "1,r1,\"long text 1\"" + recordSeparator + "2,r2,\""
+                        + longText2 + "\"" + recordSeparator, sw.toString());
+            }
+        }
+    }
+
+    @Test
+    @Disabled
+    public void testJira135_part1() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
+        final StringWriter sw = new StringWriter();
+        final List<String> list = new LinkedList<>();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            list.add("\"");
+            printer.printRecord(list);
+        }
+        final String expected = "\"\\\"\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Disabled
+    public void testJira135_part2() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
+        final StringWriter sw = new StringWriter();
+        final List<String> list = new LinkedList<>();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            list.add("\n");
+            printer.printRecord(list);
+        }
+        final String expected = "\"\\n\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Disabled
+    public void testJira135_part3() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
+        final StringWriter sw = new StringWriter();
+        final List<String> list = new LinkedList<>();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            list.add("\\");
+            printer.printRecord(list);
+        }
+        final String expected = "\"\\\\\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    @Disabled
+    public void testJira135All() throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote(DQUOTE_CHAR).withEscape(BACKSLASH);
+        final StringWriter sw = new StringWriter();
+        final List<String> list = new LinkedList<>();
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            list.add("\"");
+            list.add("\n");
+            list.add("\\");
+            printer.printRecord(list);
+        }
+        final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
+        assertEquals(expected, sw.toString());
+        final String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(list.toArray(), format), record0);
+    }
+
+    @Test
+    public void testMongoDbCsvBasic() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
+            printer.printRecord("a", "b");
+            assertEquals("a,b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbCsvCommaInValue() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
+            printer.printRecord("a,b", "c");
+            assertEquals("\"a,b\",c" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbCsvDoubleQuoteInValue() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
+            printer.printRecord("a \"c\" b", "d");
+            assertEquals("\"a \"\"c\"\" b\",d" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbCsvTabInValue() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_CSV)) {
+            printer.printRecord("a\tb", "c");
+            assertEquals("a\tb,c" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbTsvBasic() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
+            printer.printRecord("a", "b");
+            assertEquals("a\tb" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbTsvCommaInValue() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
+            printer.printRecord("a,b", "c");
+            assertEquals("a,b\tc" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMongoDbTsvTabInValue() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.MONGODB_TSV)) {
+            printer.printRecord("a\tb", "c");
+            assertEquals("\"a\tb\"\tc" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testMultiLineComment() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
+            printer.printComment("This is a comment\non multiple lines");
+
+            assertEquals("# This is a comment" + recordSeparator + "# on multiple lines" + recordSeparator,
+                    sw.toString());
+        }
+    }
+
+    @Test
+    public void testMySqlNullOutput() throws IOException {
+        Object[] s = new String[] { "NULL", null };
+        CSVFormat format = CSVFormat.MYSQL.withQuote(DQUOTE_CHAR).withNullString("NULL")
+            .withQuoteMode(QuoteMode.NON_NUMERIC);
+        StringWriter writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        String expected = "\"NULL\"\tNULL\n";
+        assertEquals(expected, writer.toString());
+        String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(s, record0);
+
+        s = new String[] { "\\N", null };
+        format = CSVFormat.MYSQL.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "A" };
+        format = CSVFormat.MYSQL.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\n", "A" };
+        format = CSVFormat.MYSQL.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\n\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.MYSQL.withNullString("NULL");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\tNULL\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.MYSQL;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "", "\u000e,\\\r" };
+        format = CSVFormat.MYSQL;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "NULL", "\\\r" };
+        format = CSVFormat.MYSQL;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "NULL\t\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\\r" };
+        format = CSVFormat.MYSQL;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+    }
+
+    @Test
+    public void testMySqlNullStringDefault() {
+        assertEquals("\\N", CSVFormat.MYSQL.getNullString());
+    }
+
+    @Test
+    public void testNewCsvPrinterAppendableNullFormat() {
+        assertThrows(NullPointerException.class, () -> new CSVPrinter(new StringWriter(), null));
+    }
+
+    @Test
+    public void testNewCsvPrinterNullAppendableFormat() {
+        assertThrows(NullPointerException.class, () -> new CSVPrinter(null, CSVFormat.DEFAULT));
+    }
+
+    @Test
+    public void testNotFlushable() throws IOException {
+        final Appendable out = new StringBuilder();
+        try (final CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b", "c");
+            assertEquals("a,b,c" + recordSeparator, out.toString());
+            printer.flush();
+        }
+    }
+
+    @Test
+    public void testParseCustomNullValues() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
+        try (final CSVPrinter printer = new CSVPrinter(sw, format)) {
+            printer.printRecord("a", null, "b");
+        }
+        final String csvString = sw.toString();
+        assertEquals("a,NULL,b" + recordSeparator, csvString);
+        try (final CSVParser iterable = format.parse(new StringReader(csvString))) {
+            final Iterator<CSVRecord> iterator = iterable.iterator();
+            final CSVRecord record = iterator.next();
+            assertEquals("a", record.get(0));
+            assertNull(record.get(1));
+            assertEquals("b", record.get(2));
+            assertFalse(iterator.hasNext());
+        }
+    }
+
+    @Test
+    public void testPlainEscaped() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'))) {
+            printer.print("abc");
+            printer.print("xyz");
+            assertEquals("abc,xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testPlainPlain() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null))) {
+            printer.print("abc");
+            printer.print("xyz");
+            assertEquals("abc,xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testPlainQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
+            printer.print("abc");
+            assertEquals("abc", sw.toString());
+        }
+    }
+
+    @Test
+    @Disabled
+    public void testPostgreSqlCsvNullOutput() throws IOException {
+        Object[] s = new String[] { "NULL", null };
+        CSVFormat format = CSVFormat.POSTGRESQL_CSV.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL);
+        StringWriter writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        String expected = "\"NULL\",NULL\n";
+        assertEquals(expected, writer.toString());
+        String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(new Object[2], record0);
+
+        s = new String[] { "\\N", null };
+        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "A" };
+        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\n", "A" };
+        format = CSVFormat.POSTGRESQL_CSV.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\n\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.POSTGRESQL_CSV.withNullString("NULL");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\tNULL\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.POSTGRESQL_CSV;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "", "\u000e,\\\r" };
+        format = CSVFormat.POSTGRESQL_CSV;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "NULL", "\\\r" };
+        format = CSVFormat.POSTGRESQL_CSV;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "NULL\t\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\\r" };
+        format = CSVFormat.POSTGRESQL_CSV;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+    }
+
+    @Test
+    @Disabled
+    public void testPostgreSqlCsvTextOutput() throws IOException {
+        Object[] s = new String[] { "NULL", null };
+        CSVFormat format = CSVFormat.POSTGRESQL_TEXT.withQuote(DQUOTE_CHAR).withNullString("NULL").withQuoteMode(QuoteMode.ALL_NON_NULL);
+        StringWriter writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        String expected = "\"NULL\"\tNULL\n";
+        assertEquals(expected, writer.toString());
+        String[] record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(new Object[2], record0);
+
+        s = new String[] { "\\N", null };
+        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "A" };
+        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\n", "A" };
+        format = CSVFormat.POSTGRESQL_TEXT.withNullString("\\N");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\n\tA\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.POSTGRESQL_TEXT.withNullString("NULL");
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\tNULL\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "", null };
+        format = CSVFormat.POSTGRESQL_TEXT;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\t\\N\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\N", "", "\u000e,\\\r" };
+        format = CSVFormat.POSTGRESQL_TEXT;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\N\t\t\u000e,\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "NULL", "\\\r" };
+        format = CSVFormat.POSTGRESQL_TEXT;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "NULL\t\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+
+        s = new String[] { "\\\r" };
+        format = CSVFormat.POSTGRESQL_TEXT;
+        writer = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(writer, format)) {
+            printer.printRecord(s);
+        }
+        expected = "\\\\\\r\n";
+        assertEquals(expected, writer.toString());
+        record0 = toFirstRecordValues(expected, format);
+        assertArrayEquals(expectNulls(s, format), record0);
+    }
+
+    @Test
+    public void testPostgreSqlNullStringDefaultCsv() {
+        assertEquals("", CSVFormat.POSTGRESQL_CSV.getNullString());
+    }
+
+    @Test
+    public void testPostgreSqlNullStringDefaultText() {
+        assertEquals("\\N", CSVFormat.POSTGRESQL_TEXT.getNullString());
+    }
+
+    @Test
+    public void testPrint() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(sw)) {
+            printer.printRecord("a", "b\\c");
+            assertEquals("a,b\\c" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrintCSVParser() throws IOException {
+        final String code = "a1,b1\n" // 1)
+            + "a2,b2\n" // 2)
+            + "a3,b3\n" // 3)
+            + "a4,b4\n"// 4)
+        ;
+        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
+        final CSVFormat format = CSVFormat.DEFAULT;
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
+            printer.printRecords(parser);
+        }
+        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
+            final List<CSVRecord> records = parser.getRecords();
+            assertFalse(records.isEmpty());
+            Utils.compare("Fail", res, records);
+        }
+    }
+
+    @Test
+    public void testPrintCSVRecord() throws IOException {
+        final String code = "a1,b1\n" // 1)
+            + "a2,b2\n" // 2)
+            + "a3,b3\n" // 3)
+            + "a4,b4\n"// 4)
+        ;
+        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
+        final CSVFormat format = CSVFormat.DEFAULT;
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
+            for (final CSVRecord record : parser) {
+                printer.printRecord(record);
+            }
+        }
+        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
+            final List<CSVRecord> records = parser.getRecords();
+            assertFalse(records.isEmpty());
+            Utils.compare("Fail", res, records);
+        }
+    }
+
+    @Test
+    public void testPrintCSVRecords() throws IOException {
+        final String code = "a1,b1\n" // 1)
+            + "a2,b2\n" // 2)
+            + "a3,b3\n" // 3)
+            + "a4,b4\n"// 4)
+        ;
+        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
+        final CSVFormat format = CSVFormat.DEFAULT;
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
+            printer.printRecords(parser.getRecords());
+        }
+        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
+            final List<CSVRecord> records = parser.getRecords();
+            assertFalse(records.isEmpty());
+            Utils.compare("Fail", res, records);
+        }
+    }
+
+    @Test
+    public void testPrintCustomNullValues() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"))) {
+            printer.printRecord("a", null, "b");
+            assertEquals("a,NULL,b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter1() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b");
+            assertEquals("a,b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter2() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a,b", "b");
+            assertEquals("\"a,b\",b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter3() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a, b", "b ");
+            assertEquals("\"a, b\",\"b \"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter4() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b\"c");
+            assertEquals("a,\"b\"\"c\"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter5() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b\nc");
+            assertEquals("a,\"b\nc\"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter6() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b\r\nc");
+            assertEquals("a,\"b\r\nc\"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrinter7() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", "b\\c");
+            assertEquals("a,b\\c" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrintNullValues() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+            printer.printRecord("a", null, "b");
+            assertEquals("a,,b" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testPrintOnePositiveInteger() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.MINIMAL))) {
+            printer.print(Integer.MAX_VALUE);
+            assertEquals(String.valueOf(Integer.MAX_VALUE), sw.toString());
+        }
+    }
+
+    /**
+     * Test to target the use of {@link IOUtils#copy(java.io.Reader, Appendable)} which directly
+     * buffers the value from the Reader to the Appendable.
+     *
+     * <p>Requires the format to have no quote or escape character, value to be a
+     * {@link java.io.Reader Reader} and the output <i>MUST NOT</i> be a
+     * {@link java.io.Writer Writer} but some other Appendable.</p>
+     *
+     * @throws IOException Not expected to happen
+     */
+    @Test
+    public void testPrintReaderWithoutQuoteToAppendable() throws IOException {
+        final StringBuilder sb = new StringBuilder();
+        final String content = "testValue";
+        try (final CSVPrinter printer = new CSVPrinter(sb, CSVFormat.DEFAULT.withQuote(null))) {
+            final StringReader value = new StringReader(content);
+            printer.print(value);
+        }
+        assertEquals(content, sb.toString());
+    }
+
+    /**
+     * Test to target the use of {@link IOUtils#copyLarge(java.io.Reader, Writer)} which directly
+     * buffers the value from the Reader to the Writer.
+     *
+     * <p>Requires the format to have no quote or escape character, value to be a
+     * {@link java.io.Reader Reader} and the output <i>MUST</i> be a
+     * {@link java.io.Writer Writer}.</p>
+     *
+     * @throws IOException Not expected to happen
+     */
+    @Test
+    public void testPrintReaderWithoutQuoteToWriter() throws IOException {
+        final StringWriter sw = new StringWriter();
+        final String content = "testValue";
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null))) {
+            final StringReader value = new StringReader(content);
+            printer.print(value);
+        }
+        assertEquals(content, sw.toString());
+    }
+
+    @Test
+    public void testPrintRecordStream() throws IOException {
+        final String code = "a1,b1\n" // 1)
+            + "a2,b2\n" // 2)
+            + "a3,b3\n" // 3)
+            + "a4,b4\n"// 4)
+        ;
+        final String[][] res = {{"a1", "b1"}, {"a2", "b2"}, {"a3", "b3"}, {"a4", "b4"}};
+        final CSVFormat format = CSVFormat.DEFAULT;
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = format.print(sw); final CSVParser parser = CSVParser.parse(code, format)) {
+            for (final CSVRecord record : parser) {
+                printer.printRecord(record.stream());
+            }
+        }
+        try (final CSVParser parser = CSVParser.parse(sw.toString(), format)) {
+            final List<CSVRecord> records = parser.getRecords();
+            assertFalse(records.isEmpty());
+            Utils.compare("Fail", res, records);
+        }
+    }
+
+    @Test
+    public void testPrintRecordsWithCSVRecord() throws IOException {
+        final String[] values = {"A", "B", "C"};
+        final String rowData = StringUtils.join(values, ',');
+        final CharArrayWriter charArrayWriter = new CharArrayWriter(0);
+        try (final CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(rowData));
+            final CSVPrinter csvPrinter = CSVFormat.INFORMIX_UNLOAD.print(charArrayWriter)) {
+            for (final CSVRecord record : parser) {
+                csvPrinter.printRecord(record);
+            }
+        }
+        assertEquals(6, charArrayWriter.size());
+        assertEquals("A|B|C" + CSVFormat.INFORMIX_UNLOAD.getRecordSeparator(), charArrayWriter.toString());
+    }
+
+    @Test
+    public void testPrintRecordsWithEmptyVector() throws IOException {
+        final PrintStream out = System.out;
+        try {
+            System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM));
+            try (CSVPrinter csvPrinter = CSVFormat.POSTGRESQL_TEXT.printer()) {
+                final Vector<CSVFormatTest.EmptyEnum> vector = new Vector<>();
+                final int expectedCapacity = 23;
+                vector.setSize(expectedCapacity);
+                csvPrinter.printRecords(vector);
+                assertEquals(expectedCapacity, vector.capacity());
+            }
+        } finally {
+            System.setOut(out);
+        }
+    }
+
+    @Test
+    public void testPrintRecordsWithObjectArray() throws IOException {
+        final CharArrayWriter charArrayWriter = new CharArrayWriter(0);
+        try (CSVPrinter csvPrinter = CSVFormat.INFORMIX_UNLOAD.print(charArrayWriter)) {
+            final HashSet<BatchUpdateException> hashSet = new HashSet<>();
+            final Object[] objectArray = new Object[6];
+            objectArray[3] = hashSet;
+            csvPrinter.printRecords(objectArray);
+        }
+        assertEquals(6, charArrayWriter.size());
+        assertEquals("\n\n\n\n\n\n", charArrayWriter.toString());
+    }
+
+    @Test
+    public void testPrintRecordsWithResultSetOneRow() throws IOException, SQLException {
+        try (CSVPrinter csvPrinter = CSVFormat.MYSQL.printer()) {
+            try (ResultSet resultSet = new SimpleResultSet()) {
+                csvPrinter.printRecords(resultSet);
+                assertEquals(0, resultSet.getRow());
+            }
+        }
+    }
+
+    @Test
+    public void testPrintToFileWithCharsetUtf16Be() throws IOException {
+        final File file = File.createTempFile(getClass().getName(), ".csv");
+        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file, StandardCharsets.UTF_16BE)) {
+            printer.printRecord("a", "b\\c");
+        }
+        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, StandardCharsets.UTF_16BE));
+    }
+
+    @Test
+    public void testPrintToFileWithDefaultCharset() throws IOException {
+        final File file = File.createTempFile(getClass().getName(), ".csv");
+        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file, Charset.defaultCharset())) {
+            printer.printRecord("a", "b\\c");
+        }
+        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, Charset.defaultCharset()));
+    }
+
+    @Test
+    public void testPrintToPathWithDefaultCharset() throws IOException {
+        final File file = File.createTempFile(getClass().getName(), ".csv");
+        try (final CSVPrinter printer = CSVFormat.DEFAULT.print(file.toPath(), Charset.defaultCharset())) {
+            printer.printRecord("a", "b\\c");
+        }
+        assertEquals("a,b\\c" + recordSeparator, FileUtils.readFileToString(file, Charset.defaultCharset()));
+    }
+
+    @Test
+    public void testQuoteAll() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.ALL))) {
+            printer.printRecord("a", "b\nc", "d");
+            assertEquals("\"a\",\"b\nc\",\"d\"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testQuoteCommaFirstChar() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.RFC4180)) {
+            printer.printRecord(",");
+            assertEquals("\",\"" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testQuoteNonNumeric() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuoteMode(QuoteMode.NON_NUMERIC))) {
+            printer.printRecord("a", "b\nc", Integer.valueOf(1));
+            assertEquals("\"a\",\"b\nc\",1" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testRandomDefault() throws Exception {
+        doRandom(CSVFormat.DEFAULT, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    public void testRandomExcel() throws Exception {
+        doRandom(CSVFormat.EXCEL, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    @Disabled
+    public void testRandomMongoDbCsv() throws Exception {
+        doRandom(CSVFormat.MONGODB_CSV, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    public void testRandomMySql() throws Exception {
+        doRandom(CSVFormat.MYSQL, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    @Disabled
+    public void testRandomOracle() throws Exception {
+        doRandom(CSVFormat.ORACLE, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    @Disabled
+    public void testRandomPostgreSqlCsv() throws Exception {
+        doRandom(CSVFormat.POSTGRESQL_CSV, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    @Disabled
+    public void testRandomPostgreSqlText() throws Exception {
+        doRandom(CSVFormat.POSTGRESQL_TEXT, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+
+    @Test
+    public void testRandomRfc4180() throws Exception {
+        doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    public void testRandomTdf() throws Exception {
+        doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
+    }
+
+    @Test
+    public void testSingleLineComment() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withCommentMarker('#'))) {
+            printer.printComment("This is a comment");
+            assertEquals("# This is a comment" + recordSeparator, sw.toString());
+        }
+    }
+
+    @Test
+    public void testSingleQuoteQuoted() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''))) {
+            printer.print("a'b'c");
+            printer.print("xyz");
+            assertEquals("'a''b''c',xyz", sw.toString());
+        }
+    }
+
+    @Test
+    public void testSkipHeaderRecordFalse() throws IOException {
+        // functionally identical to testHeader, used to test CSV-153
+        final StringWriter sw = new StringWriter();
+        try (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());
+        }
+    }
+
+    @Test
+    public void testSkipHeaderRecordTrue() throws IOException {
+        // functionally identical to testHeaderNotSet, used to test CSV-153
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw,
+                CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(true))) {
+            printer.printRecord("a", "b", "c");
+            printer.printRecord("x", "y", "z");
+            assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
+        }
+    }
+
+    @Test
+    public void testTrailingDelimiterOnTwoColumns() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrailingDelimiter())) {
+            printer.printRecord("A", "B");
+            assertEquals("A,B,\r\n", sw.toString());
+        }
+    }
+
+    @Test
+    public void testTrimOffOneColumn() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim(false))) {
+            printer.print(" A ");
+            assertEquals("\" A \"", sw.toString());
+        }
+    }
+
+    @Test
+    public void testTrimOnOneColumn() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim())) {
+            printer.print(" A ");
+            assertEquals("A", sw.toString());
+        }
+    }
+
+    @Test
+    public void testTrimOnTwoColumns() throws IOException {
+        final StringWriter sw = new StringWriter();
+        try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim())) {
+            printer.print(" A ");
+            printer.print(" B ");
+            assertEquals("A,B", sw.toString());
+        }
+    }
+
+    private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException {
+        return CSVParser.parse(expected, format).getRecords().get(0).values();
+    }
+
+    private void tryFormat(final List<String> list, final Character quote, final Character escape, final String expected) throws IOException {
+        final CSVFormat format = CSVFormat.DEFAULT.withQuote(quote).withEscape(escape).withRecordSeparator(null);
+        final Appendable out = new StringBuilder();
+        try (final CSVPrinter printer = new CSVPrinter(out, format)) {
+            printer.printRecord(list);
+        }
+        assertEquals(expected, out.toString());
+    }
+}