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 * Prints values in a {@link CSVFormat CSV format}. 045 * 046 * <p>Values can be appended to the output by calling the {@link #print(Object)} method. 047 * Values are printed according to {@link String#valueOf(Object)}. 048 * To complete a record the {@link #println()} method has to be called. 049 * Comments can be appended by calling {@link #printComment(String)}. 050 * However a comment will only be written to the output if the {@link CSVFormat} supports comments. 051 * </p> 052 * 053 * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)} 054 * or {@link #printRecord(Iterable)}. 055 * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)} 056 * methods can be used to print several records at once. 057 * </p> 058 * 059 * <p>Example:</p> 060 * 061 * <pre> 062 * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) { 063 * printer.printRecord("id", "userName", "firstName", "lastName", "birthday"); 064 * printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15)); 065 * printer.println(); 066 * printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29)); 067 * } catch (IOException ex) { 068 * ex.printStackTrace(); 069 * } 070 * </pre> 071 * 072 * <p>This code will write the following to csv.txt:</p> 073 * <pre> 074 * id,userName,firstName,lastName,birthday 075 * 1,john73,John,Doe,1973-09-15 076 * 077 * 2,mary,Mary,Meyer,1985-03-29 078 * </pre> 079 */ 080public final class CSVPrinter implements Flushable, Closeable { 081 082 /** The place that the values get written. */ 083 private final Appendable appendable; 084 085 private final CSVFormat format; 086 087 /** True if we just began a new record. */ 088 private boolean newRecord = true; 089 090 private long recordCount; 091 092 private final ReentrantLock lock = new ReentrantLock(); 093 094 /** 095 * Creates a printer that will print values to the given stream following the CSVFormat. 096 * <p> 097 * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation and escaping with a different 098 * character) are not supported. 099 * </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}