1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.orangesignal.csv;
18
19 import java.io.BufferedWriter;
20 import java.io.Closeable;
21 import java.io.Flushable;
22 import java.io.IOException;
23 import java.io.OutputStreamWriter;
24 import java.io.Writer;
25 import java.nio.charset.Charset;
26 import java.util.ArrayList;
27 import java.util.List;
28
29
30
31
32
33
34 public class CsvWriter implements Closeable, Flushable {
35
36
37
38
39 private Writer out;
40
41
42
43
44 private CsvConfig cfg;
45
46
47
48
49 private boolean utf8bom;
50
51
52
53
54 private int countNumberOfColumns = -1;
55
56 private static final int DEFAULT_CHAR_BUFFER_SIZE = 8192;
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public CsvWriter(final Writer out, final int sz, final CsvConfig cfg) {
71 if (cfg == null) {
72 throw new IllegalArgumentException("CsvConfig must not be null");
73 }
74 cfg.validate();
75 this.out = new BufferedWriter(out, sz);
76 this.cfg = cfg;
77
78 if (cfg.isUtf8bomPolicy()) {
79 final String s;
80 if (out instanceof OutputStreamWriter) {
81 s = ((OutputStreamWriter) out).getEncoding();
82 } else {
83 s = Charset.defaultCharset().name();
84 }
85 this.utf8bom = s.toLowerCase().matches("^utf\\-{0,1}8$");
86 }
87 }
88
89
90
91
92
93
94
95
96
97 public CsvWriter(final Writer out, final CsvConfig cfg) {
98 this(out, DEFAULT_CHAR_BUFFER_SIZE, cfg);
99 }
100
101
102
103
104
105
106
107
108 public CsvWriter(final Writer out, final int sz) {
109 this(out, sz, new CsvConfig());
110 }
111
112
113
114
115
116
117 public CsvWriter(final Writer out) {
118 this(out, DEFAULT_CHAR_BUFFER_SIZE, new CsvConfig());
119 }
120
121
122
123
124
125
126 private void ensureOpen() throws IOException {
127 if (out == null) {
128 throw new IOException("Stream closed");
129 }
130 }
131
132 private static final int BOM = 0xFEFF;
133
134
135
136
137
138
139
140
141 public void writeValues(final List<String> values) throws IOException {
142 synchronized (this) {
143 ensureOpen();
144
145 if (utf8bom) {
146 out.write(BOM);
147 utf8bom = false;
148 }
149
150 final StringBuilder buf = new StringBuilder();
151 if (values != null) {
152 final int max = values.size();
153 for (int i = 0; i < max; i++) {
154 if (i > 0) {
155 buf.append(cfg.getSeparator());
156 }
157
158 String value = values.get(i);
159 boolean enclose = false;
160 if (value == null) {
161
162 if (cfg.getNullString() == null) {
163 continue;
164 }
165 value = cfg.getNullString();
166 } else if (!cfg.isQuoteDisabled()) {
167
168 switch (cfg.getQuotePolicy()) {
169 case ALL:
170 enclose = true;
171 break;
172
173 case MINIMAL:
174 default:
175
176 enclose = value.indexOf(cfg.getSeparator()) != -1
177 || value.indexOf(cfg.getQuote()) != -1
178 || value.indexOf('\r') != -1 || value.indexOf('\n') != -1;
179 break;
180 }
181 } else {
182
183 final String s = escapeSeparator(value);
184 if (!value.equals(s) && cfg.isEscapeDisabled()) {
185 throw new IOException();
186 }
187 value = s;
188 }
189
190 if (enclose) {
191 buf.append(cfg.getQuote());
192 final String s = escapeQuote(value);
193 if (!value.equals(s) && cfg.isEscapeDisabled()) {
194 throw new IOException();
195 }
196 buf.append(s);
197 buf.append(cfg.getQuote());
198 } else {
199 buf.append(value);
200 }
201 }
202 }
203 if (values != null || !cfg.isIgnoreEmptyLines()) {
204 buf.append(cfg.getLineSeparator());
205 out.write(buf.toString());
206 }
207 if (!cfg.isVariableColumns() && values != null) {
208 if (countNumberOfColumns >= 0 && countNumberOfColumns != values.size()) {
209 throw new CsvValueException(String.format("Invalid column count."), values);
210 }
211 countNumberOfColumns = values.size();
212 }
213 }
214 }
215
216
217
218
219
220
221
222
223 public void writeTokens(final List<CsvToken> tokens) throws IOException {
224 if (tokens != null) {
225 final List<String> values = new ArrayList<String>(tokens.size());
226 for (final CsvToken token : tokens) {
227 if (token == null) {
228 values.add(null);
229 } else {
230 values.add(token.getValue());
231 }
232 }
233 writeValues(values);
234 } else {
235 writeValues(null);
236 }
237 }
238
239
240
241
242
243
244
245 private String escapeSeparator(final String value) {
246 return value.replace(
247 new StringBuilder(1).append(cfg.getSeparator()),
248 new StringBuilder(2).append(cfg.getEscape()).append(cfg.getSeparator())
249 );
250 }
251
252
253
254
255
256
257
258 private String escapeQuote(final String value) {
259 return value.replace(
260 new StringBuilder(1).append(cfg.getQuote()),
261 new StringBuilder(2).append(cfg.getEscape()).append(cfg.getQuote())
262 );
263 }
264
265 @Override
266 public void flush() throws IOException {
267 synchronized (this) {
268 ensureOpen();
269 out.flush();
270 }
271 }
272
273 @Override
274 public void close() throws IOException {
275 if (out != null) {
276 out.close();
277 out = null;
278 cfg = null;
279 }
280 }
281
282 }