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