001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.csv; 019 020import static org.apache.commons.csv.Constants.CR; 021import static org.apache.commons.csv.Constants.LF; 022import static org.apache.commons.csv.Constants.SP; 023 024import java.io.Closeable; 025import java.io.Flushable; 026import java.io.IOException; 027import java.sql.Clob; 028import java.sql.ResultSet; 029import java.sql.SQLException; 030import java.util.Arrays; 031import java.util.Objects; 032 033/** 034 * Prints values in a {@link CSVFormat CSV format}. 035 * 036 * <p>Values can be appended to the output by calling the {@link #print(Object)} method. 037 * Values are printed according to {@link String#valueOf(Object)}. 038 * To complete a record the {@link #println()} method has to be called. 039 * Comments can be appended by calling {@link #printComment(String)}. 040 * However a comment will only be written to the output if the {@link CSVFormat} supports comments. 041 * </p> 042 * 043 * <p>The printer also supports appending a complete record at once by calling {@link #printRecord(Object...)} 044 * or {@link #printRecord(Iterable)}. 045 * Furthermore {@link #printRecords(Object...)}, {@link #printRecords(Iterable)} and {@link #printRecords(ResultSet)} 046 * methods can be used to print several records at once. 047 * </p> 048 * 049 * <p>Example:</p> 050 * 051 * <pre> 052 * try (CSVPrinter printer = new CSVPrinter(new FileWriter("csv.txt"), CSVFormat.EXCEL)) { 053 * printer.printRecord("id", "userName", "firstName", "lastName", "birthday"); 054 * printer.printRecord(1, "john73", "John", "Doe", LocalDate.of(1973, 9, 15)); 055 * printer.println(); 056 * printer.printRecord(2, "mary", "Mary", "Meyer", LocalDate.of(1985, 3, 29)); 057 * } catch (IOException ex) { 058 * ex.printStackTrace(); 059 * } 060 * </pre> 061 * 062 * <p>This code will write the following to csv.txt:</p> 063 * <pre> 064 * id,userName,firstName,lastName,birthday 065 * 1,john73,John,Doe,1973-09-15 066 * 067 * 2,mary,Mary,Meyer,1985-03-29 068 * </pre> 069 */ 070public final class CSVPrinter implements Flushable, Closeable { 071 072 /** The place that the values get written. */ 073 private final Appendable appendable; 074 private final CSVFormat format; 075 076 /** True if we just began a new record. */ 077 private boolean newRecord = true; 078 079 /** 080 * Creates a printer that will print values to the given stream following the CSVFormat. 081 * <p> 082 * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation 083 * and escaping with a different character) are not supported. 084 * </p> 085 * 086 * @param appendable 087 * stream to which to print. Must not be null. 088 * @param format 089 * the CSV format. Must not be null. 090 * @throws IOException 091 * thrown if the optional header cannot be printed. 092 * @throws IllegalArgumentException 093 * thrown if the parameters of the format are inconsistent or if either out or format are null. 094 */ 095 public CSVPrinter(final Appendable appendable, final CSVFormat format) throws IOException { 096 Objects.requireNonNull(appendable, "appendable"); 097 Objects.requireNonNull(format, "format"); 098 099 this.appendable = appendable; 100 this.format = format.copy(); 101 // TODO: Is it a good idea to do this here instead of on the first call to a print method? 102 // It seems a pain to have to track whether the header has already been printed or not. 103 if (format.getHeaderComments() != null) { 104 for (final String line : format.getHeaderComments()) { 105 this.printComment(line); 106 } 107 } 108 if (format.getHeader() != null && !format.getSkipHeaderRecord()) { 109 this.printRecord((Object[]) format.getHeader()); 110 } 111 } 112 113 @Override 114 public void close() throws IOException { 115 close(false); 116 } 117 118 /** 119 * Closes the underlying stream with an optional flush first. 120 * @param flush whether to flush before the actual close. 121 * 122 * @throws IOException 123 * If an I/O error occurs 124 * @since 1.6 125 */ 126 public void close(final boolean flush) throws IOException { 127 if (flush || format.getAutoFlush()) { 128 flush(); 129 } 130 if (appendable instanceof Closeable) { 131 ((Closeable) appendable).close(); 132 } 133 } 134 135 /** 136 * Flushes the underlying stream. 137 * 138 * @throws IOException 139 * If an I/O error occurs 140 */ 141 @Override 142 public void flush() throws IOException { 143 if (appendable instanceof Flushable) { 144 ((Flushable) appendable).flush(); 145 } 146 } 147 148 /** 149 * Gets the target Appendable. 150 * 151 * @return the target Appendable. 152 */ 153 public Appendable getOut() { 154 return this.appendable; 155 } 156 157 /** 158 * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed. 159 * 160 * @param value 161 * value to be output. 162 * @throws IOException 163 * If an I/O error occurs 164 */ 165 public void print(final Object value) throws IOException { 166 format.print(value, appendable, newRecord); 167 newRecord = false; 168 } 169 170 /** 171 * Prints a comment on a new line among the delimiter separated values. 172 * 173 * <p> 174 * Comments will always begin on a new line and occupy at least one full line. The character specified to start 175 * comments and a space will be inserted at the beginning of each new line in the comment. 176 * </p> 177 * 178 * <p> 179 * If comments are disabled in the current CSV format this method does nothing. 180 * </p> 181 * 182 * <p>This method detects line breaks inside the comment string and inserts {@link CSVFormat#getRecordSeparator()} 183 * to start a new line of the comment. Note that this might produce unexpected results for formats that do not use 184 * line breaks as record separator.</p> 185 * 186 * @param comment 187 * the comment to output 188 * @throws IOException 189 * If an I/O error occurs 190 */ 191 public void printComment(final String comment) throws IOException { 192 if (comment == null || !format.isCommentMarkerSet()) { 193 return; 194 } 195 if (!newRecord) { 196 println(); 197 } 198 appendable.append(format.getCommentMarker().charValue()); 199 appendable.append(SP); 200 for (int i = 0; i < comment.length(); i++) { 201 final char c = comment.charAt(i); 202 switch (c) { 203 case CR: 204 if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) { 205 i++; 206 } 207 //$FALL-THROUGH$ break intentionally excluded. 208 case LF: 209 println(); 210 appendable.append(format.getCommentMarker().charValue()); 211 appendable.append(SP); 212 break; 213 default: 214 appendable.append(c); 215 break; 216 } 217 } 218 println(); 219 } 220 221 /** 222 * Prints headers for a result set based on its metadata. 223 * 224 * @param resultSet The result set to query for metadata. 225 * @throws IOException If an I/O error occurs. 226 * @throws SQLException If a database access error occurs or this method is called on a closed result set. 227 * @since 1.9.0 228 */ 229 public void printHeaders(final ResultSet resultSet) throws IOException, SQLException { 230 printRecord((Object[]) format.builder().setHeader(resultSet).build().getHeader()); 231 } 232 233 /** 234 * Outputs the record separator. 235 * 236 * @throws IOException 237 * If an I/O error occurs 238 */ 239 public void println() throws IOException { 240 format.println(appendable); 241 newRecord = true; 242 } 243 244 /** 245 * Prints the given values a single record of delimiter separated values followed by the record separator. 246 * 247 * <p> 248 * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record 249 * separator to the output after printing the record, so there is no need to call {@link #println()}. 250 * </p> 251 * 252 * @param values 253 * values to output. 254 * @throws IOException 255 * If an I/O error occurs 256 */ 257 public void printRecord(final Iterable<?> values) throws IOException { 258 for (final Object value : values) { 259 print(value); 260 } 261 println(); 262 } 263 264 /** 265 * Prints the given values a single record of delimiter separated values followed by the record separator. 266 * 267 * <p> 268 * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record 269 * separator to the output after printing the record, so there is no need to call {@link #println()}. 270 * </p> 271 * 272 * @param values 273 * values to output. 274 * @throws IOException 275 * If an I/O error occurs 276 */ 277 public void printRecord(final Object... values) throws IOException { 278 printRecord(Arrays.asList(values)); 279 } 280 281 /** 282 * Prints all the objects in the given collection handling nested collections/arrays as records. 283 * 284 * <p> 285 * If the given collection only contains simple objects, this method will print a single record like 286 * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements 287 * will each be printed as records using {@link #printRecord(Object...)}. 288 * </p> 289 * 290 * <p> 291 * Given the following data structure: 292 * </p> 293 * 294 * <pre> 295 * <code> 296 * List<String[]> data = ... 297 * data.add(new String[]{ "A", "B", "C" }); 298 * data.add(new String[]{ "1", "2", "3" }); 299 * data.add(new String[]{ "A1", "B2", "C3" }); 300 * </code> 301 * </pre> 302 * 303 * <p> 304 * Calling this method will print: 305 * </p> 306 * 307 * <pre> 308 * <code> 309 * A, B, C 310 * 1, 2, 3 311 * A1, B2, C3 312 * </code> 313 * </pre> 314 * 315 * @param values 316 * the values to print. 317 * @throws IOException 318 * If an I/O error occurs 319 */ 320 public void printRecords(final Iterable<?> values) throws IOException { 321 for (final Object value : values) { 322 if (value instanceof Object[]) { 323 this.printRecord((Object[]) value); 324 } else if (value instanceof Iterable) { 325 this.printRecord((Iterable<?>) value); 326 } else { 327 this.printRecord(value); 328 } 329 } 330 } 331 332 /** 333 * Prints all the objects in the given array handling nested collections/arrays as records. 334 * 335 * <p> 336 * If the given array only contains simple objects, this method will print a single record like 337 * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested 338 * elements will each be printed as records using {@link #printRecord(Object...)}. 339 * </p> 340 * 341 * <p> 342 * Given the following data structure: 343 * </p> 344 * 345 * <pre> 346 * <code> 347 * String[][] data = new String[3][] 348 * data[0] = String[]{ "A", "B", "C" }; 349 * data[1] = new String[]{ "1", "2", "3" }; 350 * data[2] = new String[]{ "A1", "B2", "C3" }; 351 * </code> 352 * </pre> 353 * 354 * <p> 355 * Calling this method will print: 356 * </p> 357 * 358 * <pre> 359 * <code> 360 * A, B, C 361 * 1, 2, 3 362 * A1, B2, C3 363 * </code> 364 * </pre> 365 * 366 * @param values 367 * the values to print. 368 * @throws IOException 369 * If an I/O error occurs 370 */ 371 public void printRecords(final Object... values) throws IOException { 372 printRecords(Arrays.asList(values)); 373 } 374 375 /** 376 * Prints all the objects in the given JDBC result set. 377 * 378 * @param resultSet 379 * result set the values to print. 380 * @throws IOException 381 * If an I/O error occurs 382 * @throws SQLException 383 * if a database access error occurs 384 */ 385 public void printRecords(final ResultSet resultSet) throws SQLException, IOException { 386 final int columnCount = resultSet.getMetaData().getColumnCount(); 387 while (resultSet.next()) { 388 for (int i = 1; i <= columnCount; i++) { 389 final Object object = resultSet.getObject(i); 390 // TODO Who manages the Clob? The JDBC driver or must we close it? Is it driver-dependent? 391 print(object instanceof Clob ? ((Clob) object).getCharacterStream() : object); 392 } 393 println(); 394 } 395 } 396 397 /** 398 * Prints all the objects with metadata in the given JDBC result set based on the header boolean. 399 * 400 * @param resultSet source of row data. 401 * @param printHeader whether to print headers. 402 * @throws IOException If an I/O error occurs 403 * @throws SQLException if a database access error occurs 404 * @since 1.9.0 405 */ 406 public void printRecords(final ResultSet resultSet, final boolean printHeader) throws SQLException, IOException { 407 if (printHeader) { 408 printHeaders(resultSet); 409 } 410 printRecords(resultSet); 411 } 412}