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