1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package com.csvreader;
22
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStream;
26 import java.io.OutputStreamWriter;
27 import java.io.PrintWriter;
28 import java.io.Writer;
29 import java.nio.charset.Charset;
30
31 /***
32 * A stream based writer for writing delimited text data to a file or a stream.
33 */
34 public class CsvWriter {
35 private PrintWriter outputStream = null;
36
37 private String fileName = null;
38
39 private boolean firstColumn = true;
40
41 private boolean useCustomRecordDelimiter = false;
42
43 private Charset charset = null;
44
45
46 private UserSettings userSettings = new UserSettings();
47
48 private boolean initialized = false;
49
50 private boolean closed = false;
51
52 /***
53 * Double up the text qualifier to represent an occurance of the text
54 * qualifier.
55 */
56 public static final int ESCAPE_MODE_DOUBLED = 1;
57
58 /***
59 * Use a backslash character before the text qualifier to represent an
60 * occurance of the text qualifier.
61 */
62 public static final int ESCAPE_MODE_BACKSLASH = 2;
63
64 /***
65 * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
66 * as the data destination.
67 *
68 * @param fileName
69 * The path to the file to output the data.
70 * @param delimiter
71 * The character to use as the column delimiter.
72 * @param charset
73 * The {@link java.nio.charset.Charset Charset} to use while
74 * writing the data.
75 */
76 public CsvWriter(String fileName, char delimiter, Charset charset) {
77 if (fileName == null) {
78 throw new IllegalArgumentException("Parameter fileName can not be null.");
79 }
80
81 if (charset == null) {
82 throw new IllegalArgumentException("Parameter charset can not be null.");
83 }
84
85 this.fileName = fileName;
86 userSettings.Delimiter = delimiter;
87 this.charset = charset;
88 }
89
90 /***
91 * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
92 * as the data destination. Uses a comma as the column delimiter and
93 * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}.
94 *
95 * @param fileName
96 * The path to the file to output the data.
97 */
98 public CsvWriter(String fileName) {
99 this(fileName, Letters.COMMA, Charset.forName("ISO-8859-1"));
100 }
101
102 /***
103 * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a Writer
104 * to write data to.
105 *
106 * @param outputStream
107 * The stream to write the column delimited data to.
108 * @param delimiter
109 * The character to use as the column delimiter.
110 */
111 public CsvWriter(Writer outputStream, char delimiter) {
112 if (outputStream == null) {
113 throw new IllegalArgumentException("Parameter outputStream can not be null.");
114 }
115
116 this.outputStream = new PrintWriter(outputStream);
117 userSettings.Delimiter = delimiter;
118 initialized = true;
119 }
120
121 /***
122 * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using an
123 * OutputStream to write data to.
124 *
125 * @param outputStream
126 * The stream to write the column delimited data to.
127 * @param delimiter
128 * The character to use as the column delimiter.
129 * @param charset
130 * The {@link java.nio.charset.Charset Charset} to use while
131 * writing the data.
132 */
133 public CsvWriter(OutputStream outputStream, char delimiter, Charset charset) {
134 this(new OutputStreamWriter(outputStream, charset), delimiter);
135 }
136
137 /***
138 * Gets the character being used as the column delimiter.
139 *
140 * @return The character being used as the column delimiter.
141 */
142 public char getDelimiter() {
143 return userSettings.Delimiter;
144 }
145
146 /***
147 * Sets the character to use as the column delimiter.
148 *
149 * @param delimiter
150 * The character to use as the column delimiter.
151 */
152 public void setDelimiter(char delimiter) {
153 userSettings.Delimiter = delimiter;
154 }
155
156 public char getRecordDelimiter() {
157 return userSettings.RecordDelimiter;
158 }
159
160 /***
161 * Sets the character to use as the record delimiter.
162 *
163 * @param recordDelimiter
164 * The character to use as the record delimiter. Default is
165 * combination of standard end of line characters for
166 * Windows, Unix, or Mac.
167 */
168 public void setRecordDelimiter(char recordDelimiter) {
169 useCustomRecordDelimiter = true;
170 userSettings.RecordDelimiter = recordDelimiter;
171 }
172
173 /***
174 * Gets the character to use as a text qualifier in the data.
175 *
176 * @return The character to use as a text qualifier in the data.
177 */
178 public char getTextQualifier() {
179 return userSettings.TextQualifier;
180 }
181
182 /***
183 * Sets the character to use as a text qualifier in the data.
184 *
185 * @param textQualifier
186 * The character to use as a text qualifier in the data.
187 */
188 public void setTextQualifier(char textQualifier) {
189 userSettings.TextQualifier = textQualifier;
190 }
191
192 /***
193 * Whether text qualifiers will be used while writing data or not.
194 *
195 * @return Whether text qualifiers will be used while writing data or not.
196 */
197 public boolean getUseTextQualifier() {
198 return userSettings.UseTextQualifier;
199 }
200
201 /***
202 * Sets whether text qualifiers will be used while writing data or not.
203 *
204 * @param useTextQualifier
205 * Whether to use a text qualifier while writing data or not.
206 */
207 public void setUseTextQualifier(boolean useTextQualifier) {
208 userSettings.UseTextQualifier = useTextQualifier;
209 }
210
211 public int getEscapeMode() {
212 return userSettings.EscapeMode;
213 }
214
215 public void setEscapeMode(int escapeMode) {
216 userSettings.EscapeMode = escapeMode;
217 }
218
219 public void setComment(char comment) {
220 userSettings.Comment = comment;
221 }
222
223 public char getComment() {
224 return userSettings.Comment;
225 }
226
227 /***
228 * Whether fields will be surrounded by the text qualifier even if the
229 * qualifier is not necessarily needed to escape this field.
230 *
231 * @return Whether fields will be forced to be qualified or not.
232 */
233 public boolean getForceQualifier() {
234 return userSettings.ForceQualifier;
235 }
236
237 /***
238 * Use this to force all fields to be surrounded by the text qualifier even
239 * if the qualifier is not necessarily needed to escape this field. Default
240 * is false.
241 *
242 * @param forceQualifier
243 * Whether to force the fields to be qualified or not.
244 */
245 public void setForceQualifier(boolean forceQualifier) {
246 userSettings.ForceQualifier = forceQualifier;
247 }
248
249 /***
250 * Writes another column of data to this record.
251 *
252 * @param content
253 * The data for the new column.
254 * @param preserveSpaces
255 * Whether to preserve leading and trailing whitespace in
256 * this column of data.
257 * @exception IOException
258 * Thrown if an error occurs while writing data to the
259 * destination stream.
260 */
261 public void write(String content, boolean preserveSpaces) throws IOException {
262 checkClosed();
263
264 checkInit();
265
266
267
268
269
270
271
272 if (!firstColumn) {
273 outputStream.write(userSettings.Delimiter);
274 }
275
276 boolean textQualify = userSettings.ForceQualifier;
277
278
279
280 if (content == null) {
281 content = "";
282 textQualify = false;
283 }
284
285 else if (content.equals("")) {
286 textQualify = true;
287 }
288
289
290 if (!preserveSpaces && content.length() > 0) {
291 content = content.trim();
292 }
293
294 if (!textQualify
295 && userSettings.UseTextQualifier
296 && (content.indexOf(userSettings.TextQualifier) > -1
297 || content.indexOf(userSettings.Delimiter) > -1
298 || (!useCustomRecordDelimiter && (content.indexOf(Letters.LF) > -1 || content
299 .indexOf(Letters.CR) > -1))
300 || (useCustomRecordDelimiter && content.indexOf(userSettings.RecordDelimiter) > -1)
301 || (firstColumn && content.length() > 0 && content.charAt(0) == userSettings.Comment) ||
302
303
304 (firstColumn && content.length() == 0))) {
305 textQualify = true;
306 }
307
308 if (userSettings.UseTextQualifier && !textQualify && content.length() > 0 && preserveSpaces) {
309 char firstLetter = content.charAt(0);
310
311 if (firstLetter == Letters.SPACE || firstLetter == Letters.TAB) {
312 textQualify = true;
313 }
314
315 if (!textQualify && content.length() > 1) {
316 char lastLetter = content.charAt(content.length() - 1);
317
318 if (lastLetter == Letters.SPACE || lastLetter == Letters.TAB) {
319 textQualify = true;
320 }
321 }
322 }
323
324 if (textQualify) {
325 outputStream.write(userSettings.TextQualifier);
326
327 if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
328 content = replace(content, "" + Letters.BACKSLASH, "" + Letters.BACKSLASH + Letters.BACKSLASH);
329 content = replace(content, "" + userSettings.TextQualifier, "" + Letters.BACKSLASH
330 + userSettings.TextQualifier);
331 } else {
332 content = replace(content, "" + userSettings.TextQualifier, "" + userSettings.TextQualifier
333 + userSettings.TextQualifier);
334 }
335 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
336 content = replace(content, "" + Letters.BACKSLASH, "" + Letters.BACKSLASH + Letters.BACKSLASH);
337 content = replace(content, "" + userSettings.Delimiter, "" + Letters.BACKSLASH + userSettings.Delimiter);
338
339 if (useCustomRecordDelimiter) {
340 content = replace(content, "" + userSettings.RecordDelimiter, "" + Letters.BACKSLASH
341 + userSettings.RecordDelimiter);
342 } else {
343 content = replace(content, "" + Letters.CR, "" + Letters.BACKSLASH + Letters.CR);
344 content = replace(content, "" + Letters.LF, "" + Letters.BACKSLASH + Letters.LF);
345 }
346
347 if (firstColumn && content.length() > 0 && content.charAt(0) == userSettings.Comment) {
348 if (content.length() > 1) {
349 content = "" + Letters.BACKSLASH + userSettings.Comment + content.substring(1);
350 } else {
351 content = "" + Letters.BACKSLASH + userSettings.Comment;
352 }
353 }
354 }
355
356 outputStream.write(content);
357
358 if (textQualify) {
359 outputStream.write(userSettings.TextQualifier);
360 }
361
362 firstColumn = false;
363 }
364
365 /***
366 * Writes another column of data to this record. Does not preserve
367 * leading and trailing whitespace in this column of data.
368 *
369 * @param content
370 * The data for the new column.
371 * @exception IOException
372 * Thrown if an error occurs while writing data to the
373 * destination stream.
374 */
375 public void write(String content) throws IOException {
376 write(content, false);
377 }
378
379 public void writeComment(String commentText) throws IOException {
380 checkClosed();
381
382 checkInit();
383
384 outputStream.write(userSettings.Comment);
385
386 outputStream.write(commentText);
387
388 if (useCustomRecordDelimiter) {
389 outputStream.write(userSettings.RecordDelimiter);
390 } else {
391 outputStream.println();
392 }
393
394 firstColumn = true;
395 }
396
397 /***
398 * Writes a new record using the passed in array of values.
399 *
400 * @param values
401 * Values to be written.
402 *
403 * @param preserveSpaces
404 * Whether to preserver leading and trailing spaces in
405 * columns while writing out to the record or not.
406 *
407 * @throws IOException
408 * Thrown if an error occurs while writing data to the
409 * destination stream.
410 */
411 public void writeRecord(String[] values, boolean preserveSpaces) throws IOException {
412 if (values != null && values.length > 0) {
413 for (int i = 0; i < values.length; i++) {
414 write(values[i], preserveSpaces);
415 }
416
417 endRecord();
418 }
419 }
420
421 /***
422 * Writes a new record using the passed in array of values.
423 *
424 * @param values
425 * Values to be written.
426 *
427 * @throws IOException
428 * Thrown if an error occurs while writing data to the
429 * destination stream.
430 */
431 public void writeRecord(String[] values) throws IOException {
432 writeRecord(values, false);
433 }
434
435 /***
436 * Ends the current record by sending the record delimiter.
437 *
438 * @exception IOException
439 * Thrown if an error occurs while writing data to the
440 * destination stream.
441 */
442 public void endRecord() throws IOException {
443 checkClosed();
444
445 checkInit();
446
447 if (useCustomRecordDelimiter) {
448 outputStream.write(userSettings.RecordDelimiter);
449 } else {
450 outputStream.println();
451 }
452
453 firstColumn = true;
454 }
455
456 /***
457 *
458 */
459 private void checkInit() throws IOException {
460 if (!initialized) {
461 if (fileName != null) {
462 outputStream = new PrintWriter(new OutputStreamWriter(new FileOutputStream(fileName), charset));
463 }
464
465 initialized = true;
466 }
467 }
468
469 /***
470 * Clears all buffers for the current writer and causes any buffered data to
471 * be written to the underlying device.
472 */
473 public void flush() {
474 outputStream.flush();
475 }
476
477 /***
478 * Closes and releases all related resources.
479 */
480 public void close() {
481 if (!closed) {
482 close(true);
483
484 closed = true;
485 }
486 }
487
488 /***
489 *
490 */
491 private void close(boolean closing) {
492 if (!closed) {
493 if (closing) {
494 charset = null;
495 }
496
497 try {
498 if (initialized) {
499 outputStream.close();
500 }
501 } catch (Exception e) {
502
503 }
504
505 outputStream = null;
506
507 closed = true;
508 }
509 }
510
511 /***
512 *
513 */
514 private void checkClosed() throws IOException {
515 if (closed) {
516 throw new IOException("This instance of the CsvWriter class has already been closed.");
517 }
518 }
519
520 /***
521 *
522 */
523 protected void finalize() {
524 close(false);
525 }
526
527 private class Letters {
528 public static final char LF = '\n';
529
530 public static final char CR = '\r';
531
532 public static final char QUOTE = '"';
533
534 public static final char COMMA = ',';
535
536 public static final char SPACE = ' ';
537
538 public static final char TAB = '\t';
539
540 public static final char POUND = '#';
541
542 public static final char BACKSLASH = '//';
543
544 public static final char NULL = '\0';
545 }
546
547 private class UserSettings {
548
549
550 public char TextQualifier;
551
552 public boolean UseTextQualifier;
553
554 public char Delimiter;
555
556 public char RecordDelimiter;
557
558 public char Comment;
559
560 public int EscapeMode;
561
562 public boolean ForceQualifier;
563
564 public UserSettings() {
565 TextQualifier = Letters.QUOTE;
566 UseTextQualifier = true;
567 Delimiter = Letters.COMMA;
568 RecordDelimiter = Letters.NULL;
569 Comment = Letters.POUND;
570 EscapeMode = ESCAPE_MODE_DOUBLED;
571 ForceQualifier = false;
572 }
573 }
574
575 public static String replace(String original, String pattern, String replace) {
576 final int len = pattern.length();
577 int found = original.indexOf(pattern);
578
579 if (found > -1) {
580 StringBuffer sb = new StringBuffer();
581 int start = 0;
582
583 while (found != -1) {
584 sb.append(original.substring(start, found));
585 sb.append(replace);
586 start = found + len;
587 found = original.indexOf(pattern, start);
588 }
589
590 sb.append(original.substring(start));
591
592 return sb.toString();
593 } else {
594 return original;
595 }
596 }
597 }