Source code
Description: source: package: org.apache.commons.csv, class: CSVPrinter

    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019
    020package org.apache.commons.csv;
    021
    022import static org.apache.commons.csv.Constants.CR;
    023import static org.apache.commons.csv.Constants.LF;
    024import static org.apache.commons.csv.Constants.SP;
    025
    026import java.io.Closeable;
    027import java.io.Flushable;
    028import java.io.IOException;
    029import java.io.InputStream;
    030import java.io.Reader;
    031import java.sql.Blob;
    032import java.sql.Clob;
    033import java.sql.ResultSet;
    034import java.sql.SQLException;
    035import java.sql.Statement;
    036import java.util.Arrays;
    037import java.util.Objects;
    038import java.util.concurrent.locks.ReentrantLock;
    039import java.util.stream.Stream;
    040
    041import org.apache.commons.io.function.IOStream;
    042
    043import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    044
    045/**
    046 * Prints values in a {@link CSVFormat CSV format}.
    047 *
    048 * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
    049 * Values are printed according to {@link String#valueOf(Object)}.
    050 * To complete a record the {@link #println()} method has to be called.
    051 * Comments can be appended by calling {@link #printComment(String)}.
    052 * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
    053 * </p>
    054 *
    055 * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
    056 * or {@link #printRecord(Iterable)}.
    057 * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
    058 * methods can be used to print several records at once.
    059 * </p>
    060 *
    061 * <p>Example:</p>
    062 *
    063 * <pre>
    064 * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
    065 *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
    066 *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
    067 *     printer.println();
    068 *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
    069 * } catch (IOException ex) {
    070 *     ex.printStackTrace();
    071 * }
    072 * </pre>
    073 *
    074 * <p>This code will write the following to csv.txt:</p>
    075 * <pre>
    076 * id,userName,firstName,lastName,birthday
    077 * 1,john73,John,Doe,1973-09-15
    078 *
    079 * 2,mary,Mary,Meyer,1985-03-29
    080 * </pre>
    081 */
    082public final class CSVPrinter implements Flushable, Closeable {
    083
    084    /** The place that the values get written. */
    085    private final Appendable appendable;
    086
    087    private final CSVFormat format;
    088
    089    /** True if we just began a new record. */
    090    private boolean newRecord = true;
    091
    092    private long recordCount;
    093
    094    private final ReentrantLock lock = new ReentrantLock();
    095
    096    /**
    097     * Creates a printer that will print values to the given stream following the CSVFormat.
    098     * <p>
    099     * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation and escaping with a different
    100     * character) are not supported.
    101     * </p>
    102     *
    103     * @param appendable stream to which to print. Must not be null.
    104     * @param format     the CSV format. Must not be null.
    105     * @throws IOException              thrown if the optional header cannot be printed.
    106     * @throws IllegalArgumentException thrown if the parameters of the format are inconsistent.
    107     * @throws NullPointerException     thrown if either parameters are null.
    108     */
    109    public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException {
    110        Objects.requireNonNull(appendable, "appendable");
    111        Objects.requireNonNull(format, "format");
    112        this.appendable = appendable;
    113        this.format = format.copy();
    114        // TODO: Is it a good idea to do this here instead of on the first call to a print method?
    115        // It seems a pain to have to track whether the header has already been printed or not.
    116        final String[] headerComments = format.getHeaderComments();
    117        if (headerComments != null) {
    118            for (final String line : headerComments) {
    119                printComment(line);
    120            }
    121        }
    122        if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
    123            this.printRecord((Object[]) format.getHeader());
    124        }
    125    }
    126
    127    @Override
    128    public void close() throws IOException {
    129        close(false);
    130    }
    131
    132    /**
    133     * Closes the underlying stream with an optional flush first.
    134     *
    135     * @param flush whether to flush before the actual close.
    136     * @throws IOException
    137     *             If an I/O error occurs
    138     * @since 1.6
    139     * @see CSVFormat#getAutoFlush()
    140     */
    141    public void close(final boolean flush) throws IOException {
    142        if (flush || format.getAutoFlush()) {
    143            flush();
    144        }
    145        if (appendable instanceof Closeable) {
    146            ((Closeable) appendable).close();
    147        }
    148    }
    149
    150    /**
    151     * Prints the record separator and increments the record count.
    152     *
    153     * @throws IOException
    154     *             If an I/O error occurs
    155     */
    156    @SuppressFBWarnings(value = "AT_NONATOMIC_OPERATIONS_ON_SHARED_VARIABLE", justification = "https://github.com/spotbugs/spotbugs/issues/3428")
    157    private void endOfRecord() throws IOException {
    158        println();
    159        recordCount++;
    160    }
    161
    162    /**
    163     * Flushes the underlying stream.
    164     *
    165     * @throws IOException
    166     *             If an I/O error occurs
    167     */
    168    @Override
    169    public void flush() throws IOException {
    170        if (appendable instanceof Flushable) {
    171            ((Flushable) appendable).flush();
    172        }
    173    }
    174
    175    /**
    176     * Gets the target Appendable.
    177     *
    178     * @return the target Appendable.
    179     */
    180    public Appendable getOut() {
    181        return appendable;
    182    }
    183
    184    /**
    185     * Gets the record count printed, this does not include comments or headers.
    186     *
    187     * @return the record count, this does not include comments or headers.
    188     * @since 1.13.0
    189     */
    190    public long getRecordCount() {
    191        return recordCount;
    192    }
    193
    194    /**
    195     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
    196     *
    197     * @param value
    198     *            value to be output.
    199     * @throws IOException
    200     *             If an I/O error occurs
    201     */
    202    public void print(final Object value) throws IOException {
    203        lock.lock();
    204        try {
    205            printRaw(value);
    206        } finally {
    207            lock.unlock();
    208        }
    209    }
    210
    211    /**
    212     * Prints a comment on a new line among the delimiter-separated values.
    213     *
    214     * <p>
    215     * Comments will always begin on a new line and occupy at least one full line. The character specified to start
    216     * comments and a space will be inserted at the beginning of each new line in the comment.
    217     * </p>
    218     *
    219     * <p>
    220     * If comments are disabled in the current CSV format this method does nothing.
    221     * </p>
    222     *
    223     * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
    224     * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
    225     * line breaks as record separators.</p>
    226     *
    227     * @param comment
    228     *            the comment to output
    229     * @throws IOException
    230     *             If an I/O error occurs
    231     */
    232    public void printComment(final String comment) throws IOException {
    233        lock.lock();
    234        try {
    235            if (comment == null || !format.isCommentMarkerSet()) {
    236                return;
    237            }
    238            if (!newRecord) {
    239                println();
    240            }
    241            appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
    242            appendable.append(SP);
    243            for (int i = 0; i < comment.length(); i++) {
    244                final char c = comment.charAt(i);
    245                switch (c) {
    246                case CR:
    247                    if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
    248                        i++;
    249                    }
    250                    // falls-through: break intentionally excluded.
    251                case LF:
    252                    println();
    253                    appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
    254                    appendable.append(SP);
    255                    break;
    256                default:
    257                    appendable.append(c);
    258                    break;
    259                }
    260            }
    261            println();
    262        } finally {
    263            lock.unlock();
    264        }
    265    }
    266
    267    /**
    268     * Prints headers for a result set based on its metadata.
    269     *
    270     * @param resultSet The ResultSet to query for metadata.
    271     * @throws IOException If an I/O error occurs.
    272     * @throws SQLException If a database access error occurs or this method is called on a closed result set.
    273     * @since 1.9.0
    274     */
    275    public void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
    276        lock.lock();
    277        try {
    278            try (IOStream<String> stream = IOStream.of(format.builder().setHeader(resultSet).get().getHeader())) {
    279                stream.forEachOrdered(this::print);
    280            }
    281            println();
    282        } finally {
    283            lock.unlock();
    284        }
    285    }
    286
    287    /**
    288     * Prints the record separator.
    289     *
    290     * @throws IOException
    291     *             If an I/O error occurs
    292     */
    293    public void println() throws IOException {
    294        lock.lock();
    295        try {
    296            format.println(appendable);
    297            newRecord = true;
    298        } finally {
    299            lock.unlock();
    300        }
    301    }
    302
    303    /**
    304     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
    305     *
    306     * @param value
    307     *            value to be output.
    308     * @throws IOException
    309     *             If an I/O error occurs
    310     */
    311    private void printRaw(final Object value) throws IOException {
    312        format.print(value, appendable, newRecord);
    313        newRecord = false;
    314    }
    315
    316    /**
    317     * Prints the given values as a single record of delimiter-separated values followed by the record separator.
    318     *
    319     * <p>
    320     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
    321     * separator to the output after printing the record, so there is no need to call {@link #println()}.
    322     * </p>
    323     *
    324     * @param values
    325     *            values to output.
    326     * @throws IOException
    327     *             If an I/O error occurs
    328     */
    329    @SuppressWarnings("resource")
    330    public void printRecord(final Iterable<?> values) throws IOException {
    331        lock.lock();
    332        try {
    333            IOStream.of(values).forEachOrdered(this::print);
    334            endOfRecord();
    335        } finally {
    336            lock.unlock();
    337        }
    338    }
    339
    340    /**
    341     * Prints the given values as a single record of delimiter-separated values followed by the record separator.
    342     *
    343     * <p>
    344     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
    345     * separator to the output after printing the record, so there is no need to call {@link #println()}.
    346     * </p>
    347     *
    348     * @param values
    349     *            values to output.
    350     * @throws IOException
    351     *             If an I/O error occurs
    352     */
    353    public void printRecord(final Object... values) throws IOException {
    354        printRecord(Arrays.asList(values));
    355    }
    356
    357    /**
    358     * Prints the given values as a single record of delimiter-separated values followed by the record separator.
    359     *
    360     * <p>
    361     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
    362     * separator to the output after printing the record, so there is no need to call {@link #println()}.
    363     * </p>
    364     *
    365     * @param stream
    366     *            values to output.
    367     * @throws IOException
    368     *             If an I/O error occurs
    369     * @since 1.10.0
    370     */
    371    @SuppressWarnings("resource") // caller closes.
    372    public void printRecord(final Stream<?> stream) throws IOException {
    373        lock.lock();
    374        try {
    375            IOStream.adapt(stream).forEachOrdered(stream.isParallel() ? this::printRaw : this::print);
    376            endOfRecord();
    377        } finally {
    378            lock.unlock();
    379        }
    380    }
    381
    382    private void printRecordObject(final Object value) throws IOException {
    383        if (value instanceof Object[]) {
    384            this.printRecord((Object[]) value);
    385        } else if (value instanceof Iterable) {
    386            this.printRecord((Iterable<?>) value);
    387        } else {
    388            this.printRecord(value);
    389        }
    390    }
    391
    392    @SuppressWarnings("resource")
    393    private void printRecords(final IOStream<?> stream) throws IOException {
    394        format.limit(stream).forEachOrdered(this::printRecordObject);
    395    }
    396
    397    /**
    398     * Prints all the objects in the given {@link Iterable} handling nested collections/arrays as records.
    399     *
    400     * <p>
    401     * If the given Iterable only contains simple objects, this method will print a single record like
    402     * {@link #printRecord(Iterable)}. If the given Iterable contains nested collections/arrays those nested elements
    403     * will each be printed as records using {@link #printRecord(Object...)}.
    404     * </p>
    405     *
    406     * <p>
    407     * Given the following data structure:
    408     * </p>
    409     *
    410     * <pre>{@code
    411     * List<String[]> data = new ArrayList<>();
    412     * data.add(new String[]{ "A", "B", "C" });
    413     * data.add(new String[]{ "1", "2", "3" });
    414     * data.add(new String[]{ "A1", "B2", "C3" });
    415     * }
    416     * </pre>
    417     *
    418     * <p>
    419     * Calling this method will print:
    420     * </p>
    421     *
    422     * <pre>
    423     * {@code
    424     * A, B, C
    425     * 1, 2, 3
    426     * A1, B2, C3
    427     * }
    428     * </pre>
    429     *
    430     * @param values
    431     *            the values to print.
    432     * @throws IOException
    433     *             If an I/O error occurs
    434     */
    435    @SuppressWarnings("resource")
    436    public void printRecords(final Iterable<?> values) throws IOException {
    437        printRecords(IOStream.of(values));
    438    }
    439
    440    /**
    441     * Prints all the objects in the given array handling nested collections/arrays as records.
    442     *
    443     * <p>
    444     * If the given array only contains simple objects, this method will print a single record like
    445     * {@link #printRecord(Object...)}. If the given collections contain nested collections or arrays, those nested
    446     * elements will each be printed as records using {@link #printRecord(Object...)}.
    447     * </p>
    448     *
    449     * <p>
    450     * Given the following data structure:
    451     * </p>
    452     *
    453     * <pre>{@code
    454     * String[][] data = new String[3][]
    455     * data[0] = String[]{ "A", "B", "C" };
    456     * data[1] = new String[]{ "1", "2", "3" };
    457     * data[2] = new String[]{ "A1", "B2", "C3" };
    458     * }
    459     * </pre>
    460     *
    461     * <p>
    462     * Calling this method will print:
    463     * </p>
    464     *
    465     * <pre>{@code
    466     * A, B, C
    467     * 1, 2, 3
    468     * A1, B2, C3
    469     * }
    470     * </pre>
    471     *
    472     * @param values
    473     *            the values to print.
    474     * @throws IOException
    475     *             If an I/O error occurs
    476     */
    477    public void printRecords(final Object... values) throws IOException {
    478        printRecords(Arrays.asList(values));
    479    }
    480
    481    /**
    482     * Prints all the objects in the given JDBC result set.
    483     * <p>
    484     * 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
    485     * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
    486     * </p>
    487     *
    488     * @param resultSet The values to print.
    489     * @throws IOException  If an I/O error occurs.
    490     * @throws SQLException Thrown when a database access error occurs.
    491     */
    492    public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
    493        final int columnCount = resultSet.getMetaData().getColumnCount();
    494        while (resultSet.next() && format.useRow(resultSet.getRow())) {
    495            lock.lock();
    496            try {
    497                for (int i = 1; i <= columnCount; i++) {
    498                    final Object object = resultSet.getObject(i);
    499                    if (object instanceof Clob) {
    500                        try (Reader reader = ((Clob) object).getCharacterStream()) {
    501                            print(reader);
    502                        }
    503                    } else if (object instanceof Blob) {
    504                        try (InputStream inputStream = ((Blob) object).getBinaryStream()) {
    505                            print(inputStream);
    506                        }
    507                    } else {
    508                        print(object);
    509                    }
    510                }
    511                endOfRecord();
    512            } finally {
    513                lock.unlock();
    514            }
    515        }
    516    }
    517
    518    /**
    519     * Prints all the objects with metadata in the given JDBC result set based on the header boolean.
    520     * <p>
    521     * 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
    522     * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
    523     * </p>
    524     *
    525     * @param resultSet source of row data.
    526     * @param printHeader whether to print headers.
    527     * @throws IOException If an I/O error occurs
    528     * @throws SQLException if a database access error occurs
    529     * @since 1.9.0
    530     */
    531    public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException {
    532        if (printHeader) {
    533            printHeaders(resultSet);
    534        }
    535        printRecords(resultSet);
    536    }
    537
    538    /**
    539     * Prints all the objects in the given {@link Stream} handling nested collections/arrays as records.
    540     *
    541     * <p>
    542     * If the given Stream only contains simple objects, this method will print a single record like
    543     * {@link #printRecord(Iterable)}. If the given Stream contains nested collections/arrays those nested elements
    544     * will each be printed as records using {@link #printRecord(Object...)}.
    545     * </p>
    546     *
    547     * <p>
    548     * Given the following data structure:
    549     * </p>
    550     *
    551     * <pre>{@code
    552     * List<String[]> data = new ArrayList<>();
    553     * data.add(new String[]{ "A", "B", "C" });
    554     * data.add(new String[]{ "1", "2", "3" });
    555     * data.add(new String[]{ "A1", "B2", "C3" });
    556     * Stream<String[]> stream = data.stream();
    557     * }
    558     * </pre>
    559     *
    560     * <p>
    561     * Calling this method will print:
    562     * </p>
    563     *
    564     * <pre>
    565     * {@code
    566     * A, B, C
    567     * 1, 2, 3
    568     * A1, B2, C3
    569     * }
    570     * </pre>
    571     *
    572     * @param values
    573     *            the values to print.
    574     * @throws IOException
    575     *             If an I/O error occurs
    576     * @since 1.10.0
    577     */
    578    @SuppressWarnings({ "resource" }) // Caller closes.
    579    public void printRecords(final Stream<?> values) throws IOException {
    580        printRecords(IOStream.adapt(values));
    581    }
    582}