View Javadoc
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  
20  package org.apache.commons.csv;
21  
22  import static org.apache.commons.csv.Constants.CR;
23  import static org.apache.commons.csv.Constants.LF;
24  import static org.apache.commons.csv.Constants.SP;
25  
26  import java.io.Closeable;
27  import java.io.Flushable;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.Reader;
31  import java.sql.Blob;
32  import java.sql.Clob;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.sql.Statement;
36  import java.util.Arrays;
37  import java.util.Objects;
38  import java.util.stream.Stream;
39  
40  import org.apache.commons.io.function.IOStream;
41  
42  /**
43   * Prints values in a {@link CSVFormat CSV format}.
44   *
45   * <p>Values can be appended to the output by calling the {@link #print(Object)} method.
46   * Values are printed according to {@link String#valueOf(Object)}.
47   * To complete a record the {@link #println()} method has to be called.
48   * Comments can be appended by calling {@link #printComment(String)}.
49   * However a comment will only be written to the output if the {@link CSVFormat} supports comments.
50   * </p>
51   *
52   * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)}
53   * or {@link #printRecord(Iterable)}.
54   * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)}
55   * methods can be used to print several records at once.
56   * </p>
57   *
58   * <p>Example:</p>
59   *
60   * <pre>
61   * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) {
62   *     printer.printRecord("id", "userName", "firstName", "lastName", "birthday");
63   *     printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15));
64   *     printer.println();
65   *     printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29));
66   * } catch (IOException ex) {
67   *     ex.printStackTrace();
68   * }
69   * </pre>
70   *
71   * <p>This code will write the following to csv.txt:</p>
72   * <pre>
73   * id,userName,firstName,lastName,birthday
74   * 1,john73,John,Doe,1973-09-15
75   *
76   * 2,mary,Mary,Meyer,1985-03-29
77   * </pre>
78   */
79  public final class CSVPrinter implements Flushable, Closeable {
80  
81      /** The place that the values get written. */
82      private final Appendable appendable;
83  
84      private final CSVFormat format;
85  
86      /** True if we just began a new record. */
87      private boolean newRecord = true;
88  
89      private long recordCount;
90  
91      /**
92       * Creates a printer that will print values to the given stream following the CSVFormat.
93       * <p>
94       * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation and escaping with a different
95       * character) are not supported.
96       * </p>
97       *
98       * @param appendable stream to which to print. Must not be null.
99       * @param format     the CSV format. Must not be null.
100      * @throws IOException              thrown if the optional header cannot be printed.
101      * @throws IllegalArgumentException thrown if the parameters of the format are inconsistent.
102      * @throws NullPointerException     thrown if either parameters are null.
103      */
104     public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException {
105         Objects.requireNonNull(appendable, "appendable");
106         Objects.requireNonNull(format, "format");
107         this.appendable = appendable;
108         this.format = format.copy();
109         // TODO: Is it a good idea to do this here instead of on the first call to a print method?
110         // It seems a pain to have to track whether the header has already been printed or not.
111         final String[] headerComments = format.getHeaderComments();
112         if (headerComments != null) {
113             for (final String line : headerComments) {
114                 printComment(line);
115             }
116         }
117         if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
118             this.printRecord((Object[]) format.getHeader());
119         }
120     }
121 
122     @Override
123     public void close() throws IOException {
124         close(false);
125     }
126 
127     /**
128      * Closes the underlying stream with an optional flush first.
129      *
130      * @param flush whether to flush before the actual close.
131      * @throws IOException
132      *             If an I/O error occurs
133      * @since 1.6
134      * @see CSVFormat#getAutoFlush()
135      */
136     public void close(final boolean flush) throws IOException {
137         if (flush || format.getAutoFlush()) {
138             flush();
139         }
140         if (appendable instanceof Closeable) {
141             ((Closeable) appendable).close();
142         }
143     }
144 
145     /**
146      * Prints the record separator and increments the record count.
147      *
148      * @throws IOException
149      *             If an I/O error occurs
150      */
151     private synchronized void endOfRecord() throws IOException {
152         println();
153         recordCount++;
154     }
155 
156     /**
157      * Flushes the underlying stream.
158      *
159      * @throws IOException
160      *             If an I/O error occurs
161      */
162     @Override
163     public void flush() throws IOException {
164         if (appendable instanceof Flushable) {
165             ((Flushable) appendable).flush();
166         }
167     }
168 
169     /**
170      * Gets the target Appendable.
171      *
172      * @return the target Appendable.
173      */
174     public Appendable getOut() {
175         return appendable;
176     }
177 
178     /**
179      * Gets the record count printed, this does not include comments or headers.
180      *
181      * @return the record count, this does not include comments or headers.
182      * @since 1.13.0
183      */
184     public long getRecordCount() {
185         return recordCount;
186     }
187 
188     /**
189      * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
190      *
191      * @param value
192      *            value to be output.
193      * @throws IOException
194      *             If an I/O error occurs
195      */
196     public synchronized void print(final Object value) throws IOException {
197         format.print(value, appendable, newRecord);
198         newRecord = false;
199     }
200 
201     /**
202      * Prints a comment on a new line among the delimiter-separated values.
203      *
204      * <p>
205      * Comments will always begin on a new line and occupy at least one full line. The character specified to start
206      * comments and a space will be inserted at the beginning of each new line in the comment.
207      * </p>
208      *
209      * <p>
210      * If comments are disabled in the current CSV format this method does nothing.
211      * </p>
212      *
213      * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()}
214      * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use
215      * line breaks as record separators.</p>
216      *
217      * @param comment
218      *            the comment to output
219      * @throws IOException
220      *             If an I/O error occurs
221      */
222     public synchronized void printComment(final String comment) throws IOException {
223         if (comment == null || !format.isCommentMarkerSet()) {
224             return;
225         }
226         if (!newRecord) {
227             println();
228         }
229         appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
230         appendable.append(SP);
231         for (int i = 0; i < comment.length(); i++) {
232             final char c = comment.charAt(i);
233             switch (c) {
234             case CR:
235                 if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
236                     i++;
237                 }
238                 // falls-through: break intentionally excluded.
239             case LF:
240                 println();
241                 appendable.append(format.getCommentMarker().charValue()); // Explicit (un)boxing is intentional
242                 appendable.append(SP);
243                 break;
244             default:
245                 appendable.append(c);
246                 break;
247             }
248         }
249         println();
250     }
251 
252     /**
253      * Prints headers for a result set based on its metadata.
254      *
255      * @param resultSet The ResultSet to query for metadata.
256      * @throws IOException If an I/O error occurs.
257      * @throws SQLException If a database access error occurs or this method is called on a closed result set.
258      * @since 1.9.0
259      */
260     public synchronized void printHeaders(final ResultSet resultSet) throws IOException, SQLException {
261         try (IOStream<String> stream = IOStream.of(format.builder().setHeader(resultSet).get().getHeader())) {
262             stream.forEachOrdered(this::print);
263         }
264         println();
265     }
266 
267     /**
268      * Prints the record separator.
269      *
270      * @throws IOException
271      *             If an I/O error occurs
272      */
273     public synchronized void println() throws IOException {
274         format.println(appendable);
275         newRecord = true;
276     }
277 
278     /**
279      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
280      *
281      * <p>
282      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
283      * separator to the output after printing the record, so there is no need to call {@link #println()}.
284      * </p>
285      *
286      * @param values
287      *            values to output.
288      * @throws IOException
289      *             If an I/O error occurs
290      */
291     @SuppressWarnings("resource")
292     public synchronized void printRecord(final Iterable<?> values) throws IOException {
293         IOStream.of(values).forEachOrdered(this::print);
294         endOfRecord();
295     }
296 
297     /**
298      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
299      *
300      * <p>
301      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
302      * separator to the output after printing the record, so there is no need to call {@link #println()}.
303      * </p>
304      *
305      * @param values
306      *            values to output.
307      * @throws IOException
308      *             If an I/O error occurs
309      */
310     public void printRecord(final Object... values) throws IOException {
311         printRecord(Arrays.asList(values));
312     }
313 
314     /**
315      * Prints the given values as a single record of delimiter-separated values followed by the record separator.
316      *
317      * <p>
318      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
319      * separator to the output after printing the record, so there is no need to call {@link #println()}.
320      * </p>
321      *
322      * @param values
323      *            values to output.
324      * @throws IOException
325      *             If an I/O error occurs
326      * @since 1.10.0
327      */
328     @SuppressWarnings("resource") // caller closes.
329     public synchronized void printRecord(final Stream<?> values) throws IOException {
330         IOStream.adapt(values).forEachOrdered(this::print);
331         endOfRecord();
332     }
333 
334     private void printRecordObject(final Object value) throws IOException {
335         if (value instanceof Object[]) {
336             this.printRecord((Object[]) value);
337         } else if (value instanceof Iterable) {
338             this.printRecord((Iterable<?>) value);
339         } else {
340             this.printRecord(value);
341         }
342     }
343 
344     @SuppressWarnings("resource")
345     private void printRecords(final IOStream<?> stream) throws IOException {
346         format.limit(stream).forEachOrdered(this::printRecordObject);
347     }
348 
349     /**
350      * Prints all the objects in the given {@link Iterable} handling nested collections/arrays as records.
351      *
352      * <p>
353      * If the given Iterable only contains simple objects, this method will print a single record like
354      * {@link #printRecord(Iterable)}. If the given Iterable contains nested collections/arrays those nested elements
355      * will each be printed as records using {@link #printRecord(Object...)}.
356      * </p>
357      *
358      * <p>
359      * Given the following data structure:
360      * </p>
361      *
362      * <pre>{@code
363      * List<String[]> data = new ArrayList<>();
364      * data.add(new String[]{ "A", "B", "C" });
365      * data.add(new String[]{ "1", "2", "3" });
366      * data.add(new String[]{ "A1", "B2", "C3" });
367      * }
368      * </pre>
369      *
370      * <p>
371      * Calling this method will print:
372      * </p>
373      *
374      * <pre>
375      * {@code
376      * A, B, C
377      * 1, 2, 3
378      * A1, B2, C3
379      * }
380      * </pre>
381      *
382      * @param values
383      *            the values to print.
384      * @throws IOException
385      *             If an I/O error occurs
386      */
387     @SuppressWarnings("resource")
388     public void printRecords(final Iterable<?> values) throws IOException {
389         printRecords(IOStream.of(values));
390     }
391 
392     /**
393      * Prints all the objects in the given array handling nested collections/arrays as records.
394      *
395      * <p>
396      * If the given array only contains simple objects, this method will print a single record like
397      * {@link #printRecord(Object...)}. If the given collections contain nested collections or arrays, those nested
398      * elements will each be printed as records using {@link #printRecord(Object...)}.
399      * </p>
400      *
401      * <p>
402      * Given the following data structure:
403      * </p>
404      *
405      * <pre>{@code
406      * String[][] data = new String[3][]
407      * data[0] = String[]{ "A", "B", "C" };
408      * data[1] = new String[]{ "1", "2", "3" };
409      * data[2] = new String[]{ "A1", "B2", "C3" };
410      * }
411      * </pre>
412      *
413      * <p>
414      * Calling this method will print:
415      * </p>
416      *
417      * <pre>{@code
418      * A, B, C
419      * 1, 2, 3
420      * A1, B2, C3
421      * }
422      * </pre>
423      *
424      * @param values
425      *            the values to print.
426      * @throws IOException
427      *             If an I/O error occurs
428      */
429     public void printRecords(final Object... values) throws IOException {
430         printRecords(Arrays.asList(values));
431     }
432 
433     /**
434      * Prints all the objects in the given JDBC result set.
435      * <p>
436      * 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
437      * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
438      * </p>
439      *
440      * @param resultSet The values to print.
441      * @throws IOException  If an I/O error occurs.
442      * @throws SQLException Thrown when a database access error occurs.
443      */
444     public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
445         final int columnCount = resultSet.getMetaData().getColumnCount();
446         while (resultSet.next() && format.useRow(resultSet.getRow())) {
447             for (int i = 1; i <= columnCount; i++) {
448                 final Object object = resultSet.getObject(i);
449                 if (object instanceof Clob) {
450                     try (Reader reader = ((Clob) object).getCharacterStream()) {
451                         print(reader);
452                     }
453                 } else if (object instanceof Blob) {
454                     try (InputStream inputStream = ((Blob) object).getBinaryStream()) {
455                         print(inputStream);
456                     }
457                 } else {
458                     print(object);
459                 }
460             }
461             endOfRecord();
462         }
463     }
464 
465     /**
466      * Prints all the objects with metadata in the given JDBC result set based on the header boolean.
467      * <p>
468      * 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
469      * through {@link Statement#setLargeMaxRows(long)} or {@link Statement#setMaxRows(int)}.
470      * </p>
471      *
472      * @param resultSet source of row data.
473      * @param printHeader whether to print headers.
474      * @throws IOException If an I/O error occurs
475      * @throws SQLException if a database access error occurs
476      * @since 1.9.0
477      */
478     public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException {
479         if (printHeader) {
480             printHeaders(resultSet);
481         }
482         printRecords(resultSet);
483     }
484 
485     /**
486      * Prints all the objects in the given {@link Stream} handling nested collections/arrays as records.
487      *
488      * <p>
489      * If the given Stream only contains simple objects, this method will print a single record like
490      * {@link #printRecord(Iterable)}. If the given Stream contains nested collections/arrays those nested elements
491      * will each be printed as records using {@link #printRecord(Object...)}.
492      * </p>
493      *
494      * <p>
495      * Given the following data structure:
496      * </p>
497      *
498      * <pre>{@code
499      * List<String[]> data = new ArrayList<>();
500      * data.add(new String[]{ "A", "B", "C" });
501      * data.add(new String[]{ "1", "2", "3" });
502      * data.add(new String[]{ "A1", "B2", "C3" });
503      * Stream<String[]> stream = data.stream();
504      * }
505      * </pre>
506      *
507      * <p>
508      * Calling this method will print:
509      * </p>
510      *
511      * <pre>
512      * {@code
513      * A, B, C
514      * 1, 2, 3
515      * A1, B2, C3
516      * }
517      * </pre>
518      *
519      * @param values
520      *            the values to print.
521      * @throws IOException
522      *             If an I/O error occurs
523      * @since 1.10.0
524      */
525     @SuppressWarnings({ "resource" }) // Caller closes.
526     public void printRecords(final Stream<?> values) throws IOException {
527         printRecords(IOStream.adapt(values));
528     }
529 }