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.Clob;
028import java.sql.ResultSet;
029import java.sql.SQLException;
030import java.util.Arrays;
031
032/**
033 * Prints values in a {@link CSVFormat CSV format}.
034 *
035 * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
036 * Values are printed according to {@link String#valueOf(Object)}.
037 * To complete a record the {@link #println()} method has to be called.
038 * Comments can be appended by calling {@link #printComment(String)}.
039 * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
040 * </p>
041 *
042 * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
043 * or {@link #printRecord(Iterable)}.
044 * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
045 * methods can be used to print several records at once.
046 * </p>
047 *
048 * <p>Example:</p>
049 *
050 * <pre>
051 * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
052 *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
053 *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
054 *     printer.println();
055 *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
056 * } catch (IOException ex) {
057 *     ex.printStackTrace();
058 * }
059 * </pre>
060 *
061 * <p>This code will write the following to csv.txt:</p>
062 * <pre>
063 * id,userName,firstName,lastName,birthday
064 * 1,john73,John,Doe,1973-09-15
065 *
066 * 2,mary,Mary,Meyer,1985-03-29
067 * </pre>
068 */
069public final class CSVPrinter implements Flushable, Closeable {
070
071    /** The place that the values get written. */
072    private final Appendable out;
073    private final CSVFormat format;
074
075    /** True if we just began a new record. */
076    private boolean newRecord = true;
077
078    /**
079     * Creates a printer that will print values to the given stream following the CSVFormat.
080     * <p>
081     * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
082     * and escaping with a different character) are not supported.
083     * </p>
084     *
085     * @param out
086     *            stream to which to print. Must not be null.
087     * @param format
088     *            the CSV format. Must not be null.
089     * @throws IOException
090     *             thrown if the optional header cannot be printed.
091     * @throws IllegalArgumentException
092     *             thrown if the parameters of the format are inconsistent or if either out or format are null.
093     */
094    public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
095        Assertions.notNull(out, "out");
096        Assertions.notNull(format, "format");
097
098        this.out = out;
099        this.format = format;
100        // TODO: Is it a good idea to do this here instead of on the first call to a print method?
101        // It seems a pain to have to track whether the header has already been printed or not.
102        if (format.getHeaderComments() != null) {
103            for (final String line : format.getHeaderComments()) {
104                if (line != null) {
105                    this.printComment(line);
106                }
107            }
108        }
109        if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
110            this.printRecord((Object[]) format.getHeader());
111        }
112    }
113
114    // ======================================================
115    // printing implementation
116    // ======================================================
117
118    @Override
119    public void close() throws IOException {
120        close(false);
121    }
122
123    /**
124     * Closes the underlying stream with an optional flush first.
125     * @param flush whether to flush before the actual close.
126     *
127     * @throws IOException
128     *             If an I/O error occurs
129     * @since 1.6
130     */
131    public void close(final boolean flush) throws IOException {
132        if (flush || format.getAutoFlush()) {
133            flush();
134        }
135        if (out instanceof Closeable) {
136            ((Closeable) out).close();
137        }
138    }
139
140    /**
141     * Flushes the underlying stream.
142     *
143     * @throws IOException
144     *             If an I/O error occurs
145     */
146    @Override
147    public void flush() throws IOException {
148        if (out instanceof Flushable) {
149            ((Flushable) out).flush();
150        }
151    }
152
153    /**
154     * Gets the target Appendable.
155     *
156     * @return the target Appendable.
157     */
158    public Appendable getOut() {
159        return this.out;
160    }
161
162    /**
163     * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
164     *
165     * @param value
166     *            value to be output.
167     * @throws IOException
168     *             If an I/O error occurs
169     */
170    public void print(final Object value) throws IOException {
171        format.print(value, out, newRecord);
172        newRecord = false;
173    }
174
175    /**
176     * Prints a comment on a new line among the delimiter separated values.
177     *
178     * <p>
179     * Comments will always begin on a new line and occupy at least one full line. The character specified to start
180     * comments and a space will be inserted at the beginning of each new line in the comment.
181     * </p>
182     *
183     * <p>
184     * If comments are disabled in the current CSV format this method does nothing.
185     * </p>
186     *
187     * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
188     * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
189     * line breaks as record separator.</p>
190     *
191     * @param comment
192     *            the comment to output
193     * @throws IOException
194     *             If an I/O error occurs
195     */
196    public void printComment(final String comment) throws IOException {
197        if (!format.isCommentMarkerSet()) {
198            return;
199        }
200        if (!newRecord) {
201            println();
202        }
203        out.append(format.getCommentMarker().charValue());
204        out.append(SP);
205        for (int i = 0; i < comment.length(); i++) {
206            final char c = comment.charAt(i);
207            switch (c) {
208            case CR:
209                if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
210                    i++;
211                }
212                //$FALL-THROUGH$ break intentionally excluded.
213            case LF:
214                println();
215                out.append(format.getCommentMarker().charValue());
216                out.append(SP);
217                break;
218            default:
219                out.append(c);
220                break;
221            }
222        }
223        println();
224    }
225
226    /**
227     * Outputs the record separator.
228     *
229     * @throws IOException
230     *             If an I/O error occurs
231     */
232    public void println() throws IOException {
233        format.println(out);
234        newRecord = true;
235    }
236
237    /**
238     * Prints the given values a single record of delimiter separated values followed by the record separator.
239     *
240     * <p>
241     * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
242     * separator to the output after printing the record, so there is no need to call {@link #println()}.
243     * </p>
244     *
245     * @param values
246     *            values to output.
247     * @throws IOException
248     *             If an I/O error occurs
249     */
250    public void printRecord(final Iterable<?> values) throws IOException {
251        for (final Object value : values) {
252            print(value);
253        }
254        println();
255    }
256
257    /**
258     * Prints the given values 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    public void printRecord(final Object... values) throws IOException {
271        format.printRecord(out, values);
272        newRecord = true;
273    }
274
275    /**
276     * Prints all the objects in the given collection handling nested collections/arrays as records.
277     *
278     * <p>
279     * If the given collection only contains simple objects, this method will print a single record like
280     * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements
281     * will each be printed as records using {@link #printRecord(Object...)}.
282     * </p>
283     *
284     * <p>
285     * Given the following data structure:
286     * </p>
287     *
288     * <pre>
289     * <code>
290     * List&lt;String[]&gt; data = ...
291     * data.add(new String[]{ "A", "B", "C" });
292     * data.add(new String[]{ "1", "2", "3" });
293     * data.add(new String[]{ "A1", "B2", "C3" });
294     * </code>
295     * </pre>
296     *
297     * <p>
298     * Calling this method will print:
299     * </p>
300     *
301     * <pre>
302     * <code>
303     * A, B, C
304     * 1, 2, 3
305     * A1, B2, C3
306     * </code>
307     * </pre>
308     *
309     * @param values
310     *            the values to print.
311     * @throws IOException
312     *             If an I/O error occurs
313     */
314    public void printRecords(final Iterable<?> values) throws IOException {
315        for (final Object value : values) {
316            if (value instanceof Object[]) {
317                this.printRecord((Object[]) value);
318            } else if (value instanceof Iterable) {
319                this.printRecord((Iterable<?>) value);
320            } else {
321                this.printRecord(value);
322            }
323        }
324    }
325
326    /**
327     * Prints all the objects in the given array handling nested collections/arrays as records.
328     *
329     * <p>
330     * If the given array only contains simple objects, this method will print a single record like
331     * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested
332     * elements will each be printed as records using {@link #printRecord(Object...)}.
333     * </p>
334     *
335     * <p>
336     * Given the following data structure:
337     * </p>
338     *
339     * <pre>
340     * <code>
341     * String[][] data = new String[3][]
342     * data[0] = String[]{ "A", "B", "C" };
343     * data[1] = new String[]{ "1", "2", "3" };
344     * data[2] = new String[]{ "A1", "B2", "C3" };
345     * </code>
346     * </pre>
347     *
348     * <p>
349     * Calling this method will print:
350     * </p>
351     *
352     * <pre>
353     * <code>
354     * A, B, C
355     * 1, 2, 3
356     * A1, B2, C3
357     * </code>
358     * </pre>
359     *
360     * @param values
361     *            the values to print.
362     * @throws IOException
363     *             If an I/O error occurs
364     */
365    public void printRecords(final Object... values) throws IOException {
366        printRecords(Arrays.asList(values));
367    }
368
369    /**
370     * Prints all the objects in the given JDBC result set.
371     *
372     * @param resultSet
373     *            result set the values to print.
374     * @throws IOException
375     *             If an I/O error occurs
376     * @throws SQLException
377     *             if a database access error occurs
378     */
379    public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
380        final int columnCount = resultSet.getMetaData().getColumnCount();
381        while (resultSet.next()) {
382            for (int i = 1; i <= columnCount; i++) {
383                final Object object = resultSet.getObject(i);
384                print(object instanceof Clob ? ((Clob) object).getCharacterStream() : object);
385            }
386            println();
387        }
388    }
389}