    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.csv;
    019
    020import static org.apache.commons.csv.Constants.CR;
    021import static org.apache.commons.csv.Constants.LF;
    022import static org.apache.commons.csv.Constants.SP;
    023
    024import java.io.Closeable;
    025import java.io.Flushable;
    026import java.io.IOException;
    027import java.sql.ResultSet;
    028import java.sql.SQLException;
    029
    030/**
    031 * Prints values in a CSV format.
    032 */
    033public final class CSVPrinter implements Flushable, Closeable {
    034
    035    /** The place that the values get written. */
    036    private final Appendable out;
    037    private final CSVFormat format;
    038
    039    /** True if we just began a new record. */
    040    private boolean newRecord = true;
    041
    042    /**
    043     * Creates a printer that will print values to the given stream following the CSVFormat.
    044     * <p>
    045     * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
    046     * and escaping with a different character) are not supported.
    047     * </p>
    048     *
    049     * @param out
    050     *            stream to which to print. Must not be null.
    051     * @param format
    052     *            the CSV format. Must not be null.
    053     * @throws IOException
    054     *             thrown if the optional header cannot be printed.
    055     * @throws IllegalArgumentException
    056     *             thrown if the parameters of the format are inconsistent or if either out or format are null.
    057     */
    058    public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
    059        Assertions.notNull(out, "out");
    060        Assertions.notNull(format, "format");
    061
    062        this.out = out;
    063        this.format = format;
    064        // TODO: Is it a good idea to do this here instead of on the first call to a print method?
    065        // It seems a pain to have to track whether the header has already been printed or not.
    066        if (format.getHeaderComments() != null) {
    067            for (final String line : format.getHeaderComments()) {
    068                if (line != null) {
    069                    this.printComment(line);
    070                }
    071            }
    072        }
    073        if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
    074            this.printRecord((Object[]) format.getHeader());
    075        }
    076    }
    077
    078    // ======================================================
    079    // printing implementation
    080    // ======================================================
    081
    082    @Override
    083    public void close() throws IOException {
    084        if (out instanceof Closeable) {
    085            ((Closeable) out).close();
    086        }
    087    }
    088
    089    /**
    090     * Flushes the underlying stream.
    091     *
    092     * @throws IOException
    093     *             If an I/O error occurs
    094     */
    095    @Override
    096    public void flush() throws IOException {
    097        if (out instanceof Flushable) {
    098            ((Flushable) out).flush();
    099        }
    100    }
    101
    102    /**
    103     * Gets the target Appendable.
    104     *
    105     * @return the target Appendable.
    106     */
    107    public Appendable getOut() {
    108        return this.out;
    109    }
    110
    111    /**
    112     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
    113     *
    114     * @param value
    115     *            value to be output.
    116     * @throws IOException
    117     *             If an I/O error occurs
    118     */
    119    public void print(final Object value) throws IOException {
    120        format.print(value, out, newRecord);
    121        newRecord = false;
    122    }
    123
    124    /**
    125     * Prints a comment on a new line among the delimiter separated values.
    126     *
    127     * <p>
    128     * Comments will always begin on a new line and occupy a least one full line. The character specified to start
    129     * comments and a space will be inserted at the beginning of each new line in the comment.
    130     * </p>
    131     *
    132     * If comments are disabled in the current CSV format this method does nothing.
    133     *
    134     * @param comment
    135     *            the comment to output
    136     * @throws IOException
    137     *             If an I/O error occurs
    138     */
    139    public void printComment(final String comment) throws IOException {
    140        if (!format.isCommentMarkerSet()) {
    141            return;
    142        }
    143        if (!newRecord) {
    144            println();
    145        }
    146        out.append(format.getCommentMarker().charValue());
    147        out.append(SP);
    148        for (int i = 0; i < comment.length(); i++) {
    149            final char c = comment.charAt(i);
    150            switch (c) {
    151            case CR:
    152                if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
    153                    i++;
    154                }
    155                //$FALL-THROUGH$ break intentionally excluded.
    156            case LF:
    157                println();
    158                out.append(format.getCommentMarker().charValue());
    159                out.append(SP);
    160                break;
    161            default:
    162                out.append(c);
    163                break;
    164            }
    165        }
    166        println();
    167    }
    168
    169    /**
    170     * Outputs the record separator.
    171     *
    172     * @throws IOException
    173     *             If an I/O error occurs
    174     */
    175    public void println() throws IOException {
    176        format.println(out);
    177        newRecord = true;
    178    }
    179
    180    /**
    181     * Prints the given values a single record of delimiter separated values followed by the record separator.
    182     *
    183     * <p>
    184     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
    185     * separator to the output after printing the record, so there is no need to call {@link #println()}.
    186     * </p>
    187     *
    188     * @param values
    189     *            values to output.
    190     * @throws IOException
    191     *             If an I/O error occurs
    192     */
    193    public void printRecord(final Iterable<?> values) throws IOException {
    194        for (final Object value : values) {
    195            print(value);
    196        }
    197        println();
    198    }
    199
    200    /**
    201     * Prints the given values a single record of delimiter separated values followed by the record separator.
    202     *
    203     * <p>
    204     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
    205     * separator to the output after printing the record, so there is no need to call {@link #println()}.
    206     * </p>
    207     *
    208     * @param values
    209     *            values to output.
    210     * @throws IOException
    211     *             If an I/O error occurs
    212     */
    213    public void printRecord(final Object... values) throws IOException {
    214        format.printRecord(out, values);
    215        newRecord = true;
    216    }
    217
    218    /**
    219     * Prints all the objects in the given collection handling nested collections/arrays as records.
    220     *
    221     * <p>
    222     * If the given collection only contains simple objects, this method will print a single record like
    223     * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements
    224     * will each be printed as records using {@link #printRecord(Object...)}.
    225     * </p>
    226     *
    227     * <p>
    228     * Given the following data structure:
    229     * </p>
    230     *
    231     * <pre>
    232     * <code>
    233     * List&lt;String[]&gt; data = ...
    234     * data.add(new String[]{ "A", "B", "C" });
    235     * data.add(new String[]{ "1", "2", "3" });
    236     * data.add(new String[]{ "A1", "B2", "C3" });
    237     * </code>
    238     * </pre>
    239     *
    240     * <p>
    241     * Calling this method will print:
    242     * </p>
    243     *
    244     * <pre>
    245     * <code>
    246     * A, B, C
    247     * 1, 2, 3
    248     * A1, B2, C3
    249     * </code>
    250     * </pre>
    251     *
    252     * @param values
    253     *            the values to print.
    254     * @throws IOException
    255     *             If an I/O error occurs
    256     */
    257    public void printRecords(final Iterable<?> values) throws IOException {
    258        for (final Object value : values) {
    259            if (value instanceof Object[]) {
    260                this.printRecord((Object[]) value);
    261            } else if (value instanceof Iterable) {
    262                this.printRecord((Iterable<?>) value);
    263            } else {
    264                this.printRecord(value);
    265            }
    266        }
    267    }
    268
    269    /**
    270     * Prints all the objects in the given array handling nested collections/arrays as records.
    271     *
    272     * <p>
    273     * If the given array only contains simple objects, this method will print a single record like
    274     * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested
    275     * elements will each be printed as records using {@link #printRecord(Object...)}.
    276     * </p>
    277     *
    278     * <p>
    279     * Given the following data structure:
    280     * </p>
    281     *
    282     * <pre>
    283     * <code>
    284     * String[][] data = new String[3][]
    285     * data[0] = String[]{ "A", "B", "C" };
    286     * data[1] = new String[]{ "1", "2", "3" };
    287     * data[2] = new String[]{ "A1", "B2", "C3" };
    288     * </code>
    289     * </pre>
    290     *
    291     * <p>
    292     * Calling this method will print:
    293     * </p>
    294     *
    295     * <pre>
    296     * <code>
    297     * A, B, C
    298     * 1, 2, 3
    299     * A1, B2, C3
    300     * </code>
    301     * </pre>
    302     *
    303     * @param values
    304     *            the values to print.
    305     * @throws IOException
    306     *             If an I/O error occurs
    307     */
    308    public void printRecords(final Object... values) throws IOException {
    309        for (final Object value : values) {
    310            if (value instanceof Object[]) {
    311                this.printRecord((Object[]) value);
    312            } else if (value instanceof Iterable) {
    313                this.printRecord((Iterable<?>) value);
    314            } else {
    315                this.printRecord(value);
    316            }
    317        }
    318    }
    319
    320    /**
    321     * Prints all the objects in the given JDBC result set.
    322     *
    323     * @param resultSet
    324     *            result set the values to print.
    325     * @throws IOException
    326     *             If an I/O error occurs
    327     * @throws SQLException
    328     *             if a database access error occurs
    329     */
    330    public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
    331        final int columnCount = resultSet.getMetaData().getColumnCount();
    332        while (resultSet.next()) {
    333            for (int i = 1; i <= columnCount; i++) {
    334                print(resultSet.getObject(i));
    335            }
    336            println();
    337        }
    338    }
    339}