1 /*
2 * Copyright 2007 Kasper B. Graversen
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.supercsv.io;
17
18 import java.io.BufferedWriter;
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.util.List;
22
23 import org.supercsv.encoder.CsvEncoder;
24 import org.supercsv.prefs.CsvPreference;
25 import org.supercsv.util.CsvContext;
26 import org.supercsv.util.Util;
27
28 /**
29 * Defines the standard behaviour of a CSV writer.
30 *
31 * @author Kasper B. Graversen
32 * @author James Bassett
33 */
34 public abstract class AbstractCsvWriter implements ICsvWriter {
35
36 private final Writer writer;
37
38 private final CsvPreference preference;
39
40 private final CsvEncoder encoder;
41
42 // the line number being written / just written
43 private int lineNumber = 0;
44
45 // the row being written / just written
46 private int rowNumber = 0;
47
48 // the column being written / just written
49 private int columnNumber = 0;
50
51 /**
52 * Constructs a new <tt>AbstractCsvWriter</tt> with the supplied writer and preferences.
53 *
54 * @param writer
55 * the stream to write to
56 * @param preference
57 * the CSV preferences
58 * @throws NullPointerException
59 * if writer or preference are null
60 */
61 public AbstractCsvWriter(final Writer writer, final CsvPreference preference) {
62 this(writer, preference, true);
63 }
64
65 /**
66 * Constructs a new <tt>AbstractCsvWriter</tt> with the supplied writer, preferences and option
67 * to wrap the writer.
68 *
69 * @param writer
70 * the stream to write to
71 * @param preference
72 * the CSV preferences
73 * @param bufferizeWriter
74 * indicates if the writer should be wrapped internally with a BufferedWriter
75 * @throws NullPointerException
76 * if writer or preference are null
77 */
78 public AbstractCsvWriter(final Writer writer, final CsvPreference preference, boolean bufferizeWriter) {
79 if( writer == null ) {
80 throw new NullPointerException("writer should not be null");
81 } else if( preference == null ) {
82 throw new NullPointerException("preference should not be null");
83 }
84
85 this.writer = bufferizeWriter ? new BufferedWriter(writer) : writer;
86 this.preference = preference;
87 this.encoder = preference.getEncoder();
88 }
89
90 /**
91 * Closes the underlying writer, flushing it first.
92 */
93 public void close() throws IOException {
94 writer.close();
95 }
96
97 /**
98 * Flushes the underlying writer.
99 */
100 public void flush() throws IOException {
101 writer.flush();
102 }
103
104 /**
105 * In order to maintain the current row and line numbers, this method <strong>must</strong> be called at the very
106 * beginning of every write method implemented in concrete CSV writers. This will allow the correct row/line numbers
107 * to be used in any exceptions thrown before writing occurs (e.g. during CellProcessor execution), and means that
108 * {@link #getLineNumber()} and {@link #getRowNumber()} can be called after writing to return the line/row just
109 * written.
110 */
111 protected void incrementRowAndLineNo() {
112 lineNumber++;
113 rowNumber++;
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 public int getLineNumber() {
120 return lineNumber;
121 }
122
123 /**
124 * {@inheritDoc}
125 */
126 public int getRowNumber() {
127 return rowNumber;
128 }
129
130 /**
131 * Writes a List of columns as a line to the CsvWriter.
132 *
133 * @param columns
134 * the columns to write
135 * @throws IllegalArgumentException
136 * if columns.size == 0
137 * @throws IOException
138 * If an I/O error occurs
139 * @throws NullPointerException
140 * if columns is null
141 */
142 protected void writeRow(final List<?> columns) throws IOException {
143 writeRow(Util.objectListToStringArray(columns));
144 }
145
146 /**
147 * Writes one or more Object columns as a line to the CsvWriter.
148 *
149 * @param columns
150 * the columns to write
151 * @throws IllegalArgumentException
152 * if columns.length == 0
153 * @throws IOException
154 * If an I/O error occurs
155 * @throws NullPointerException
156 * if columns is null
157 */
158 protected void writeRow(final Object... columns) throws IOException {
159 writeRow(Util.objectArrayToStringArray(columns));
160 }
161
162 /**
163 * Writes one or more String columns as a line to the CsvWriter.
164 *
165 * @param columns
166 * the columns to write
167 * @throws IllegalArgumentException
168 * if columns.length == 0
169 * @throws IOException
170 * If an I/O error occurs
171 * @throws NullPointerException
172 * if columns is null
173 */
174 protected void writeRow(final String... columns) throws IOException {
175
176 if( columns == null ) {
177 throw new NullPointerException(String.format("columns to write should not be null on line %d", lineNumber));
178 } else if( columns.length == 0 ) {
179 throw new IllegalArgumentException(String.format("columns to write should not be empty on line %d",
180 lineNumber));
181 }
182
183 StringBuilder builder = new StringBuilder();
184 for( int i = 0; i < columns.length; i++ ) {
185
186 columnNumber = i + 1; // column no used by CsvEncoder
187
188 if( i > 0 ) {
189 builder.append((char) preference.getDelimiterChar()); // delimiter
190 }
191
192 final String csvElement = columns[i];
193 if( csvElement != null ) {
194 final CsvContext context = new CsvContext(lineNumber, rowNumber, columnNumber);
195 final String escapedCsv = encoder.encode(csvElement, context, preference);
196 builder.append(escapedCsv);
197 lineNumber = context.getLineNumber(); // line number can increment when encoding multi-line columns
198 }
199
200 }
201
202 builder.append(preference.getEndOfLineSymbols()); // EOL
203 writer.write(builder.toString());
204 }
205
206 /**
207 * {@inheritDoc}
208 */
209 public void writeComment(final String comment) throws IOException {
210
211 lineNumber++; // we're not catering for embedded newlines (must be a single-line comment)
212
213 if( comment == null ) {
214 throw new NullPointerException(String.format("comment to write should not be null on line %d", lineNumber));
215 }
216
217 writer.write(comment + preference.getEndOfLineSymbols());
218
219 }
220
221 /**
222 * {@inheritDoc}
223 */
224 public void writeHeader(final String... header) throws IOException {
225
226 // update the current row/line numbers
227 incrementRowAndLineNo();
228
229 writeRow(header);
230 }
231
232 }