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