View Javadoc
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 }