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