CSVPrinter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  *   https://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */

  19. package org.apache.commons.csv;

  20. import static org.apache.commons.csv.Constants.CR;
  21. import static org.apache.commons.csv.Constants.LF;
  22. import static org.apache.commons.csv.Constants.SP;

  23. import java.io.Closeable;
  24. import java.io.Flushable;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.io.Reader;
  28. import java.sql.Blob;
  29. import java.sql.Clob;
  30. import java.sql.ResultSet;
  31. import java.sql.SQLException;
  32. import java.sql.Statement;
  33. import java.util.Arrays;
  34. import java.util.Objects;
  35. import java.util.stream.Stream;

  36. import org.apache.commons.io.function.IOStream;

  37. /**
  38.  * Prints values in a {@link CSVFormat CSV format}.
  39.  *
  40.  * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
  41.  * Values are printed according to {@link String#valueOf(Object)}.
  42.  * To complete a record the {@link #println()} method has to be called.
  43.  * Comments can be appended by calling {@link #printComment(String)}.
  44.  * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
  45.  * </p>
  46.  *
  47.  * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
  48.  * or {@link #printRecord(Iterable)}.
  49.  * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
  50.  * methods can be used to print several records at once.
  51.  * </p>
  52.  *
  53.  * <p>Example:</p>
  54.  *
  55.  * <pre>
  56.  * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
  57.  *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
  58.  *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
  59.  *     printer.println();
  60.  *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
  61.  * } catch (IOException ex) {
  62.  *     ex.printStackTrace();
  63.  * }
  64.  * </pre>
  65.  *
  66.  * <p>This code will write the following to csv.txt:</p>
  67.  * <pre>
  68.  * id,userName,firstName,lastName,birthday
  69.  * 1,john73,John,Doe,1973-09-15
  70.  *
  71.  * 2,mary,Mary,Meyer,1985-03-29
  72.  * </pre>
  73.  */
  74. public final class CSVPrinter implements Flushable, Closeable {

  75.     /** The place that the values get written. */
  76.     private final Appendable appendable;

  77.     private final CSVFormat format;

  78.     /** True if we just began a new record. */
  79.     private boolean newRecord = true;

  80.     private long recordCount;

  81.     /**
  82.      * Creates a printer that will print values to the given stream following the CSVFormat.
  83.      * <p>
  84.      * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation and escaping with a different
  85.      * character) are not supported.
  86.      * </p>
  87.      *
  88.      * @param appendable stream to which to print. Must not be null.
  89.      * @param format     the CSV format. Must not be null.
  90.      * @throws IOException              thrown if the optional header cannot be printed.
  91.      * @throws IllegalArgumentException thrown if the parameters of the format are inconsistent.
  92.      * @throws NullPointerException     thrown if either parameters are null.
  93.      */
  94.     public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException {
  95.         Objects.requireNonNull(appendable, "appendable");
  96.         Objects.requireNonNull(format, "format");
  97.         this.appendable = appendable;
  98.         this.format = format.copy();
  99.         // TODO: Is it a good idea to do this here instead of on the first call to a print method?
  100.         // It seems a pain to have to track whether the header has already been printed or not.
  101.         final String[] headerComments = format.getHeaderComments();
  102.         if (headerComments != null) {
  103.             for (final String line : headerComments) {
  104.                 printComment(line);
  105.             }
  106.         }
  107.         if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
  108.             this.printRecord((Object[]) format.getHeader());
  109.         }
  110.     }

  111.     @Override
  112.     public void close() throws IOException {
  113.         close(false);
  114.     }

  115.     /**
  116.      * Closes the underlying stream with an optional flush first.
  117.      *
  118.      * @param flush whether to flush before the actual close.
  119.      * @throws IOException
  120.      *             If an I/O error occurs
  121.      * @since 1.6
  122.      * @see CSVFormat#getAutoFlush()
  123.      */
  124.     public void close(final boolean flush) throws IOException {
  125.         if (flush || format.getAutoFlush()) {
  126.             flush();
  127.         }
  128.         if (appendable instanceof Closeable) {
  129.             ((Closeable) appendable).close();
  130.         }
  131.     }

  132.     /**
  133.      * Prints the record separator and increments the record count.
  134.      *
  135.      * @throws IOException
  136.      *             If an I/O error occurs
  137.      */
  138.     private synchronized void endOfRecord() throws IOException {
  139.         println();
  140.         recordCount++;
  141.     }

  142.     /**
  143.      * Flushes the underlying stream.
  144.      *
  145.      * @throws IOException
  146.      *             If an I/O error occurs
  147.      */
  148.     @Override
  149.     public void flush() throws IOException {
  150.         if (appendable instanceof Flushable) {
  151.             ((Flushable) appendable).flush();
  152.         }
  153.     }

  154.     /**
  155.      * Gets the target Appendable.
  156.      *
  157.      * @return the target Appendable.
  158.      */
  159.     public Appendable getOut() {
  160.         return appendable;
  161.     }

  162.     /**
  163.      * Gets the record count printed, this does not include comments or headers.
  164.      *
  165.      * @return the record count, this does not include comments or headers.
  166.      * @since 1.13.0
  167.      */
  168.     public long getRecordCount() {
  169.         return recordCount;
  170.     }

  171.     /**
  172.      * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
  173.      *
  174.      * @param value
  175.      *            value to be output.
  176.      * @throws IOException
  177.      *             If an I/O error occurs
  178.      */
  179.     public synchronized void print(final Object value) throws IOException {
  180.         format.print(value, appendable, newRecord);
  181.         newRecord = false;
  182.     }

  183.     /**
  184.      * Prints a comment on a new line among the delimiter-separated values.
  185.      *
  186.      * <p>
  187.      * Comments will always begin on a new line and occupy at least one full line. The character specified to start
  188.      * comments and a space will be inserted at the beginning of each new line in the comment.
  189.      * </p>
  190.      *
  191.      * <p>
  192.      * If comments are disabled in the current CSV format this method does nothing.
  193.      * </p>
  194.      *
  195.      * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
  196.      * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
  197.      * line breaks as record separators.</p>
  198.      *
  199.      * @param comment
  200.      *            the comment to output
  201.      * @throws IOException
  202.      *             If an I/O error occurs
  203.      */
  204.     public synchronized void printComment(final String comment) throws IOException {
  205.         if (comment == null || !format.isCommentMarkerSet()) {
  206.             return;
  207.         }
  208.         if (!newRecord) {
  209.             println();
  210.         }
  211.         appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
  212.         appendable.append(SP);
  213.         for (int i = 0; i < comment.length(); i++) {
  214.             final char c = comment.charAt(i);
  215.             switch (c) {
  216.             case CR:
  217.                 if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
  218.                     i++;
  219.                 }
  220.                 // falls-through: break intentionally excluded.
  221.             case LF:
  222.                 println();
  223.                 appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
  224.                 appendable.append(SP);
  225.                 break;
  226.             default:
  227.                 appendable.append(c);
  228.                 break;
  229.             }
  230.         }
  231.         println();
  232.     }

  233.     /**
  234.      * Prints headers for a result set based on its metadata.
  235.      *
  236.      * @param resultSet The ResultSet to query for metadata.
  237.      * @throws IOException If an I/O error occurs.
  238.      * @throws SQLException If a database access error occurs or this method is called on a closed result set.
  239.      * @since 1.9.0
  240.      */
  241.     public synchronized void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
  242.         try (IOStream<String> stream = IOStream.of(format.builder().setHeader(resultSet).get().getHeader())) {
  243.             stream.forEachOrdered(this::print);
  244.         }
  245.         println();
  246.     }

  247.     /**
  248.      * Prints the record separator.
  249.      *
  250.      * @throws IOException
  251.      *             If an I/O error occurs
  252.      */
  253.     public synchronized void println() throws IOException {
  254.         format.println(appendable);
  255.         newRecord = true;
  256.     }

  257.     /**
  258.      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
  259.      *
  260.      * <p>
  261.      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
  262.      * separator to the output after printing the record, so there is no need to call {@link #println()}.
  263.      * </p>
  264.      *
  265.      * @param values
  266.      *            values to output.
  267.      * @throws IOException
  268.      *             If an I/O error occurs
  269.      */
  270.     @SuppressWarnings("resource")
  271.     public synchronized void printRecord(final Iterable<?> values) throws IOException {
  272.         IOStream.of(values).forEachOrdered(this::print);
  273.         endOfRecord();
  274.     }

  275.     /**
  276.      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
  277.      *
  278.      * <p>
  279.      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
  280.      * separator to the output after printing the record, so there is no need to call {@link #println()}.
  281.      * </p>
  282.      *
  283.      * @param values
  284.      *            values to output.
  285.      * @throws IOException
  286.      *             If an I/O error occurs
  287.      */
  288.     public void printRecord(final Object... values) throws IOException {
  289.         printRecord(Arrays.asList(values));
  290.     }

  291.     /**
  292.      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
  293.      *
  294.      * <p>
  295.      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
  296.      * separator to the output after printing the record, so there is no need to call {@link #println()}.
  297.      * </p>
  298.      *
  299.      * @param values
  300.      *            values to output.
  301.      * @throws IOException
  302.      *             If an I/O error occurs
  303.      * @since 1.10.0
  304.      */
  305.     @SuppressWarnings("resource") // caller closes.
  306.     public synchronized void printRecord(final Stream<?> values) throws IOException {
  307.         IOStream.adapt(values).forEachOrdered(this::print);
  308.         endOfRecord();
  309.     }

  310.     private void printRecordObject(final Object value) throws IOException {
  311.         if (value instanceof Object[]) {
  312.             this.printRecord((Object[]) value);
  313.         } else if (value instanceof Iterable) {
  314.             this.printRecord((Iterable<?>) value);
  315.         } else {
  316.             this.printRecord(value);
  317.         }
  318.     }

  319.     @SuppressWarnings("resource")
  320.     private void printRecords(final IOStream<?> stream) throws IOException {
  321.         format.limit(stream).forEachOrdered(this::printRecordObject);
  322.     }

  323.     /**
  324.      * Prints all the objects in the given {@link Iterable} handling nested collections/arrays as records.
  325.      *
  326.      * <p>
  327.      * If the given Iterable only contains simple objects, this method will print a single record like
  328.      * {@link #printRecord(Iterable)}. If the given Iterable contains nested collections/arrays those nested elements
  329.      * will each be printed as records using {@link #printRecord(Object...)}.
  330.      * </p>
  331.      *
  332.      * <p>
  333.      * Given the following data structure:
  334.      * </p>
  335.      *
  336.      * <pre>{@code
  337.      * List<String[]> data = new ArrayList<>();
  338.      * data.add(new String[]{ "A", "B", "C" });
  339.      * data.add(new String[]{ "1", "2", "3" });
  340.      * data.add(new String[]{ "A1", "B2", "C3" });
  341.      * }
  342.      * </pre>
  343.      *
  344.      * <p>
  345.      * Calling this method will print:
  346.      * </p>
  347.      *
  348.      * <pre>
  349.      * {@code
  350.      * A, B, C
  351.      * 1, 2, 3
  352.      * A1, B2, C3
  353.      * }
  354.      * </pre>
  355.      *
  356.      * @param values
  357.      *            the values to print.
  358.      * @throws IOException
  359.      *             If an I/O error occurs
  360.      */
  361.     @SuppressWarnings("resource")
  362.     public void printRecords(final Iterable<?> values) throws IOException {
  363.         printRecords(IOStream.of(values));
  364.     }

  365.     /**
  366.      * Prints all the objects in the given array handling nested collections/arrays as records.
  367.      *
  368.      * <p>
  369.      * If the given array only contains simple objects, this method will print a single record like
  370.      * {@link #printRecord(Object...)}. If the given collections contain nested collections or arrays, those nested
  371.      * elements will each be printed as records using {@link #printRecord(Object...)}.
  372.      * </p>
  373.      *
  374.      * <p>
  375.      * Given the following data structure:
  376.      * </p>
  377.      *
  378.      * <pre>{@code
  379.      * String[][] data = new String[3][]
  380.      * data[0] = String[]{ "A", "B", "C" };
  381.      * data[1] = new String[]{ "1", "2", "3" };
  382.      * data[2] = new String[]{ "A1", "B2", "C3" };
  383.      * }
  384.      * </pre>
  385.      *
  386.      * <p>
  387.      * Calling this method will print:
  388.      * </p>
  389.      *
  390.      * <pre>{@code
  391.      * A, B, C
  392.      * 1, 2, 3
  393.      * A1, B2, C3
  394.      * }
  395.      * </pre>
  396.      *
  397.      * @param values
  398.      *            the values to print.
  399.      * @throws IOException
  400.      *             If an I/O error occurs
  401.      */
  402.     public void printRecords(final Object... values) throws IOException {
  403.         printRecords(Arrays.asList(values));
  404.     }

  405.     /**
  406.      * Prints all the objects in the given JDBC result set.
  407.      * <p>
  408.      * You can use {@link CSVFormat.Builder#setMaxRows(long)} to limit how many rows a result set produces. This is most useful when you cannot limit rows
  409.      * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
  410.      * </p>
  411.      *
  412.      * @param resultSet The values to print.
  413.      * @throws IOException  If an I/O error occurs.
  414.      * @throws SQLException Thrown when a database access error occurs.
  415.      */
  416.     public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
  417.         final int columnCount = resultSet.getMetaData().getColumnCount();
  418.         while (resultSet.next() && format.useRow(resultSet.getRow())) {
  419.             for (int i = 1; i <= columnCount; i++) {
  420.                 final Object object = resultSet.getObject(i);
  421.                 if (object instanceof Clob) {
  422.                     try (Reader reader = ((Clob) object).getCharacterStream()) {
  423.                         print(reader);
  424.                     }
  425.                 } else if (object instanceof Blob) {
  426.                     try (InputStream inputStream = ((Blob) object).getBinaryStream()) {
  427.                         print(inputStream);
  428.                     }
  429.                 } else {
  430.                     print(object);
  431.                 }
  432.             }
  433.             endOfRecord();
  434.         }
  435.     }

  436.     /**
  437.      * Prints all the objects with metadata in the given JDBC result set based on the header boolean.
  438.      * <p>
  439.      * You can use {@link CSVFormat.Builder#setMaxRows(long)} to limit how many rows a result set produces. This is most useful when you cannot limit rows
  440.      * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
  441.      * </p>
  442.      *
  443.      * @param resultSet source of row data.
  444.      * @param printHeader whether to print headers.
  445.      * @throws IOException If an I/O error occurs
  446.      * @throws SQLException if a database access error occurs
  447.      * @since 1.9.0
  448.      */
  449.     public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException {
  450.         if (printHeader) {
  451.             printHeaders(resultSet);
  452.         }
  453.         printRecords(resultSet);
  454.     }

  455.     /**
  456.      * Prints all the objects in the given {@link Stream} handling nested collections/arrays as records.
  457.      *
  458.      * <p>
  459.      * If the given Stream only contains simple objects, this method will print a single record like
  460.      * {@link #printRecord(Iterable)}. If the given Stream contains nested collections/arrays those nested elements
  461.      * will each be printed as records using {@link #printRecord(Object...)}.
  462.      * </p>
  463.      *
  464.      * <p>
  465.      * Given the following data structure:
  466.      * </p>
  467.      *
  468.      * <pre>{@code
  469.      * List<String[]> data = new ArrayList<>();
  470.      * data.add(new String[]{ "A", "B", "C" });
  471.      * data.add(new String[]{ "1", "2", "3" });
  472.      * data.add(new String[]{ "A1", "B2", "C3" });
  473.      * Stream<String[]> stream = data.stream();
  474.      * }
  475.      * </pre>
  476.      *
  477.      * <p>
  478.      * Calling this method will print:
  479.      * </p>
  480.      *
  481.      * <pre>
  482.      * {@code
  483.      * A, B, C
  484.      * 1, 2, 3
  485.      * A1, B2, C3
  486.      * }
  487.      * </pre>
  488.      *
  489.      * @param values
  490.      *            the values to print.
  491.      * @throws IOException
  492.      *             If an I/O error occurs
  493.      * @since 1.10.0
  494.      */
  495.     @SuppressWarnings({ "resource" }) // Caller closes.
  496.     public void printRecords(final Stream<?> values) throws IOException {
  497.         printRecords(IOStream.adapt(values));
  498.     }
  499. }