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  public final class CSVPrinter implements Flushable, Closeable {
34  
35      /** The place that the values get written. */
36      private final Appendable out;
37      private final CSVFormat format;
38  
39      /** True if we just began a new record. */
40      private boolean newRecord = true;
41  
42      /**
43       * Creates a printer that will print values to the given stream following the CSVFormat.
44       * <p>
45       * Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats (encapsulation
46       * and escaping with a different character) are not supported.
47       * </p>
48       *
49       * @param out
50       *            stream to which to print. Must not be null.
51       * @param format
52       *            the CSV format. Must not be null.
53       * @throws IOException
54       *             thrown if the optional header cannot be printed.
55       * @throws IllegalArgumentException
56       *             thrown if the parameters of the format are inconsistent or if either out or format are null.
57       */
58      public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
59          Assertions.notNull(out, "out");
60          Assertions.notNull(format, "format");
61  
62          this.out = out;
63          this.format = format;
64          // TODO: Is it a good idea to do this here instead of on the first call to a print method?
65          // It seems a pain to have to track whether the header has already been printed or not.
66          if (format.getHeaderComments() != null) {
67              for (final String line : format.getHeaderComments()) {
68                  if (line != null) {
69                      this.printComment(line);
70                  }
71              }
72          }
73          if (format.getHeader() != null && !format.getSkipHeaderRecord()) {
74              this.printRecord((Object[]) format.getHeader());
75          }
76      }
77  
78      // ======================================================
79      // printing implementation
80      // ======================================================
81  
82      @Override
83      public void close() throws IOException {
84          if (out instanceof Closeable) {
85              ((Closeable) out).close();
86          }
87      }
88  
89      /**
90       * Flushes the underlying stream.
91       *
92       * @throws IOException
93       *             If an I/O error occurs
94       */
95      @Override
96      public void flush() throws IOException {
97          if (out instanceof Flushable) {
98              ((Flushable) out).flush();
99          }
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&lt;String[]&gt; 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 }