View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.csv;
19  
20  import static org.apache.commons.csv.Constants.CR;
21  import static org.apache.commons.csv.Constants.LF;
22  import static org.apache.commons.csv.Constants.SP;
23  
24  import java.io.Closeable;
25  import java.io.Flushable;
26  import java.io.IOException;
27  import java.sql.ResultSet;
28  import java.sql.SQLException;
29  
30  /**
31   * Prints values in a CSV format.
32   *
33   * @version $Id$
34   */
35  public final class CSVPrinter implements Flushable, Closeable {
36  
37      /** The place that the values get written. */
38      private final Appendable out;
39      private final CSVFormat format;
40  
41      /** True if we just began a new record. */
42      private boolean newRecord = true;
43  
44      /**
45       * Creates a printer that will print values to the given stream following the CSVFormat.
46       * <p>
47       * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
48       * and escaping with a different character) are not supported.
49       * </p>
50       *
51       * @param out
52       *            stream to which to print. Must not be null.
53       * @param format
54       *            the CSV format. Must not be null.
55       * @throws IOException
56       *             thrown if the optional header cannot be printed.
57       * @throws IllegalArgumentException
58       *             thrown if the parameters of the format are inconsistent or if either out or format are null.
59       */
60      public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
61          Assertions.notNull(out, "out");
62          Assertions.notNull(format, "format");
63  
64          this.out = out;
65          this.format = format;
66          // TODO: Is it a good idea to do this here instead of on the first call to a print method?
67          // It seems a pain to have to track whether the header has already been printed or not.
68          if (format.getHeaderComments() != null) {
69              for (final String line : format.getHeaderComments()) {
70                  if (line != null) {
71                      this.printComment(line);
72                  }
73              }
74          }
75          if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
76              this.printRecord((Object[]) format.getHeader());
77          }
78      }
79  
80      // ======================================================
81      // printing implementation
82      // ======================================================
83  
84      @Override
85      public void close() throws IOException {
86          if (out instanceof Closeable) {
87              ((Closeable) out).close();
88          }
89      }
90  
91      /**
92       * Flushes the underlying stream.
93       *
94       * @throws IOException
95       *             If an I/O error occurs
96       */
97      @Override
98      public void flush() throws IOException {
99          if (out instanceof Flushable) {
100             ((Flushable) out).flush();
101         }
102     }
103 
104     /**
105      * Gets the target Appendable.
106      *
107      * @return the target Appendable.
108      */
109     public Appendable getOut() {
110         return this.out;
111     }
112 
113     /**
114      * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed.
115      *
116      * @param value
117      *            value to be output.
118      * @throws IOException
119      *             If an I/O error occurs
120      */
121     public void print(final Object value) throws IOException {
122         format.print(value, out, newRecord);
123         newRecord = false;
124     }
125 
126     /**
127      * Prints a comment on a new line among the delimiter separated values.
128      *
129      * <p>
130      * Comments will always begin on a new line and occupy a least one full line. The character specified to start
131      * comments and a space will be inserted at the beginning of each new line in the comment.
132      * </p>
133      *
134      * If comments are disabled in the current CSV format this method does nothing.
135      *
136      * @param comment
137      *            the comment to output
138      * @throws IOException
139      *             If an I/O error occurs
140      */
141     public void printComment(final String comment) throws IOException {
142         if (!format.isCommentMarkerSet()) {
143             return;
144         }
145         if (!newRecord) {
146             println();
147         }
148         out.append(format.getCommentMarker().charValue());
149         out.append(SP);
150         for (int i = 0; i < comment.length(); i++) {
151             final char c = comment.charAt(i);
152             switch (c) {
153             case CR:
154                 if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) {
155                     i++;
156                 }
157                 //$FALL-THROUGH$ break intentionally excluded.
158             case LF:
159                 println();
160                 out.append(format.getCommentMarker().charValue());
161                 out.append(SP);
162                 break;
163             default:
164                 out.append(c);
165                 break;
166             }
167         }
168         println();
169     }
170 
171     /**
172      * Outputs the record separator.
173      *
174      * @throws IOException
175      *             If an I/O error occurs
176      */
177     public void println() throws IOException {
178         format.println(out);
179         newRecord = true;
180     }
181 
182     /**
183      * Prints the given values a single record of delimiter separated values followed by the record separator.
184      *
185      * <p>
186      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
187      * separator to the output after printing the record, so there is no need to call {@link #println()}.
188      * </p>
189      *
190      * @param values
191      *            values to output.
192      * @throws IOException
193      *             If an I/O error occurs
194      */
195     public void printRecord(final Iterable<?> values) throws IOException {
196         for (final Object value : values) {
197             print(value);
198         }
199         println();
200     }
201 
202     /**
203      * Prints the given values a single record of delimiter separated values followed by the record separator.
204      *
205      * <p>
206      * The values will be quoted if needed. Quotes and newLine characters will be escaped. This method adds the record
207      * separator to the output after printing the record, so there is no need to call {@link #println()}.
208      * </p>
209      *
210      * @param values
211      *            values to output.
212      * @throws IOException
213      *             If an I/O error occurs
214      */
215     public void printRecord(final Object... values) throws IOException {
216         format.printRecord(out, values);
217         newRecord = true;
218     }
219 
220     /**
221      * Prints all the objects in the given collection handling nested collections/arrays as records.
222      *
223      * <p>
224      * If the given collection only contains simple objects, this method will print a single record like
225      * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements
226      * will each be printed as records using {@link #printRecord(Object...)}.
227      * </p>
228      *
229      * <p>
230      * Given the following data structure:
231      * </p>
232      *
233      * <pre>
234      * <code>
235      * List&lt;String[]&gt; data = ...
236      * data.add(new String[]{ "A", "B", "C" });
237      * data.add(new String[]{ "1", "2", "3" });
238      * data.add(new String[]{ "A1", "B2", "C3" });
239      * </code>
240      * </pre>
241      *
242      * <p>
243      * Calling this method will print:
244      * </p>
245      *
246      * <pre>
247      * <code>
248      * A, B, C
249      * 1, 2, 3
250      * A1, B2, C3
251      * </code>
252      * </pre>
253      *
254      * @param values
255      *            the values to print.
256      * @throws IOException
257      *             If an I/O error occurs
258      */
259     public void printRecords(final Iterable<?> values) throws IOException {
260         for (final Object value : values) {
261             if (value instanceof Object[]) {
262                 this.printRecord((Object[]) value);
263             } else if (value instanceof Iterable) {
264                 this.printRecord((Iterable<?>) value);
265             } else {
266                 this.printRecord(value);
267             }
268         }
269     }
270 
271     /**
272      * Prints all the objects in the given array handling nested collections/arrays as records.
273      *
274      * <p>
275      * If the given array only contains simple objects, this method will print a single record like
276      * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested
277      * elements will each be printed as records using {@link #printRecord(Object...)}.
278      * </p>
279      *
280      * <p>
281      * Given the following data structure:
282      * </p>
283      *
284      * <pre>
285      * <code>
286      * String[][] data = new String[3][]
287      * data[0] = String[]{ "A", "B", "C" };
288      * data[1] = new String[]{ "1", "2", "3" };
289      * data[2] = new String[]{ "A1", "B2", "C3" };
290      * </code>
291      * </pre>
292      *
293      * <p>
294      * Calling this method will print:
295      * </p>
296      *
297      * <pre>
298      * <code>
299      * A, B, C
300      * 1, 2, 3
301      * A1, B2, C3
302      * </code>
303      * </pre>
304      *
305      * @param values
306      *            the values to print.
307      * @throws IOException
308      *             If an I/O error occurs
309      */
310     public void printRecords(final Object... values) throws IOException {
311         for (final Object value : values) {
312             if (value instanceof Object[]) {
313                 this.printRecord((Object[]) value);
314             } else if (value instanceof Iterable) {
315                 this.printRecord((Iterable<?>) value);
316             } else {
317                 this.printRecord(value);
318             }
319         }
320     }
321 
322     /**
323      * Prints all the objects in the given JDBC result set.
324      *
325      * @param resultSet
326      *            result set the values to print.
327      * @throws IOException
328      *             If an I/O error occurs
329      * @throws SQLException
330      *             if a database access error occurs
331      */
332     public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
333         final int columnCount = resultSet.getMetaData().getColumnCount();
334         while (resultSet.next()) {
335             for (int i = 1; i <= columnCount; i++) {
336                 print(resultSet.getObject(i));
337             }
338             println();
339         }
340     }
341 }