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 }