001package au.com.bytecode.opencsv;
002
003/**
004 Copyright 2005 Bytecode Pty Ltd.
005
006 Licensed under the Apache License, Version 2.0 (the "License");
007 you may not use this file except in compliance with the License.
008 You may obtain a copy of the License at
009
010 http://www.apache.org/licenses/LICENSE-2.0
011
012 Unless required by applicable law or agreed to in writing, software
013 distributed under the License is distributed on an "AS IS" BASIS,
014 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 See the License for the specific language governing permissions and
016 limitations under the License.
017 */
018
019import java.io.IOException;
020import java.io.PrintWriter;
021import java.io.Reader;
022import java.io.Writer;
023import java.math.BigDecimal;
024import java.sql.Clob;
025import java.sql.ResultSet;
026import java.sql.ResultSetMetaData;
027import java.sql.SQLException;
028import java.sql.Time;
029import java.sql.Timestamp;
030import java.sql.Types;
031import java.text.SimpleDateFormat;
032import java.util.Iterator;
033import java.util.List;
034
035/**
036 * A very simple CSV writer released under a commercial-friendly license.
037 *
038 * @author Glen Smith
039 *
040 */
041public class CSVWriter {
042    
043    private Writer rawWriter;
044
045    private PrintWriter pw;
046
047    private char separator;
048
049    private char quotechar;
050    
051    private char escapechar;
052    
053    private String lineEnd;
054
055    /** The character used for escaping quotes. */
056    public static final char DEFAULT_ESCAPE_CHARACTER = '"';
057
058    /** The default separator to use if none is supplied to the constructor. */
059    public static final char DEFAULT_SEPARATOR = ',';
060
061    /**
062     * The default quote character to use if none is supplied to the
063     * constructor.
064     */
065    public static final char DEFAULT_QUOTE_CHARACTER = '"';
066    
067    /** The quote constant to use when you wish to suppress all quoting. */
068    public static final char NO_QUOTE_CHARACTER = '\u0000';
069    
070    /** The escape constant to use when you wish to suppress all escaping. */
071    public static final char NO_ESCAPE_CHARACTER = '\u0000';
072    
073    /** Default line terminator uses platform encoding. */
074    public static final String DEFAULT_LINE_END = "\n";
075
076    private static final SimpleDateFormat
077        TIMESTAMP_FORMATTER = 
078                new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
079
080    private static final SimpleDateFormat
081        DATE_FORMATTER = 
082                new SimpleDateFormat("dd-MMM-yyyy");
083    
084    /**
085     * Constructs CSVWriter using a comma for the separator.
086     *
087     * @param writer
088     *            the writer to an underlying CSV source.
089     */
090    public CSVWriter(Writer writer) {
091        this(writer, DEFAULT_SEPARATOR);
092    }
093
094    /**
095     * Constructs CSVWriter with supplied separator.
096     *
097     * @param writer
098     *            the writer to an underlying CSV source.
099     * @param separator
100     *            the delimiter to use for separating entries.
101     */
102    public CSVWriter(Writer writer, char separator) {
103        this(writer, separator, DEFAULT_QUOTE_CHARACTER);
104    }
105
106    /**
107     * Constructs CSVWriter with supplied separator and quote char.
108     *
109     * @param writer
110     *            the writer to an underlying CSV source.
111     * @param separator
112     *            the delimiter to use for separating entries
113     * @param quotechar
114     *            the character to use for quoted elements
115     */
116    public CSVWriter(Writer writer, char separator, char quotechar) {
117        this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER);
118    }
119
120    /**
121     * Constructs CSVWriter with supplied separator and quote char.
122     *
123     * @param writer
124     *            the writer to an underlying CSV source.
125     * @param separator
126     *            the delimiter to use for separating entries
127     * @param quotechar
128     *            the character to use for quoted elements
129     * @param escapechar
130     *            the character to use for escaping quotechars or escapechars
131     */
132    public CSVWriter(Writer writer, char separator, char quotechar, char escapechar) {
133        this(writer, separator, quotechar, escapechar, DEFAULT_LINE_END);
134    }
135    
136    
137    /**
138     * Constructs CSVWriter with supplied separator and quote char.
139     *
140     * @param writer
141     *            the writer to an underlying CSV source.
142     * @param separator
143     *            the delimiter to use for separating entries
144     * @param quotechar
145     *            the character to use for quoted elements
146     * @param lineEnd
147     *                    the line feed terminator to use
148     */
149    public CSVWriter(Writer writer, char separator, char quotechar, String lineEnd) {
150        this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER, lineEnd);
151    }   
152    
153    
154    
155    /**
156     * Constructs CSVWriter with supplied separator, quote char, escape char and line ending.
157     *
158     * @param writer
159     *            the writer to an underlying CSV source.
160     * @param separator
161     *            the delimiter to use for separating entries
162     * @param quotechar
163     *            the character to use for quoted elements
164     * @param escapechar
165     *            the character to use for escaping quotechars or escapechars
166     * @param lineEnd
167     *                    the line feed terminator to use
168     */
169    public CSVWriter(Writer writer, char separator, char quotechar, char escapechar, String lineEnd) {
170        this.rawWriter = writer;
171        this.pw = new PrintWriter(writer);
172        this.separator = separator;
173        this.quotechar = quotechar;
174        this.escapechar = escapechar;
175        this.lineEnd = lineEnd;
176    }
177    
178    /**
179     * Writes the entire list to a CSV file. The list is assumed to be a
180     * String[]
181     *
182     * @param allLines
183     *            a List of String[], with each String[] representing a line of
184     *            the file.
185     */
186    public void writeAll(List allLines)  {
187
188        for (Iterator iter = allLines.iterator(); iter.hasNext();) {
189            String[] nextLine = (String[]) iter.next();
190            writeNext(nextLine);
191        }
192
193    }
194
195    protected void writeColumnNames(ResultSetMetaData metadata)
196        throws SQLException {
197        
198        int columnCount =  metadata.getColumnCount();
199        
200        String[] nextLine = new String[columnCount];
201                for (int i = 0; i < columnCount; i++) {
202                        nextLine[i] = metadata.getColumnName(i + 1);
203                }
204        writeNext(nextLine);
205    }
206    
207    /**
208     * Writes the entire ResultSet to a CSV file.
209     *
210     * The caller is responsible for closing the ResultSet.
211     *
212     * @param rs the recordset to write
213     * @param includeColumnNames true if you want column names in the output, false otherwise
214     *
215     */
216    public void writeAll(java.sql.ResultSet rs, boolean includeColumnNames)  throws SQLException, IOException {
217        
218        ResultSetMetaData metadata = rs.getMetaData();
219        
220        
221        if (includeColumnNames) {
222                        writeColumnNames(metadata);
223                }
224
225        int columnCount =  metadata.getColumnCount();
226        
227        while (rs.next())
228        {
229                String[] nextLine = new String[columnCount];
230                
231                for (int i = 0; i < columnCount; i++) {
232                                nextLine[i] = getColumnValue(rs, metadata.getColumnType(i + 1), i + 1);
233                        }
234                
235                writeNext(nextLine);
236        }
237    }
238    
239    private static String getColumnValue(ResultSet rs, int colType, int colIndex)
240                throws SQLException, IOException {
241
242        String value = "";
243        
244                switch (colType)
245                {
246                        case Types.BIT:
247                                Object bit = rs.getObject(colIndex);
248                                if (bit != null) {
249                                        value = String.valueOf(bit);
250                                }
251                        break;
252                        case Types.BOOLEAN:
253                                boolean b = rs.getBoolean(colIndex);
254                                if (!rs.wasNull()) {
255                                        value = Boolean.valueOf(b).toString();
256                                }
257                        break;
258                        case Types.CLOB:
259                                Clob c = rs.getClob(colIndex);
260                                if (c != null) {
261                                        value = read(c);
262                                }
263                        break;
264                        case Types.BIGINT:
265                        case Types.DECIMAL:
266                        case Types.DOUBLE:
267                        case Types.FLOAT:
268                        case Types.REAL:
269                        case Types.NUMERIC:
270                                BigDecimal bd = rs.getBigDecimal(colIndex);
271                                if (bd != null) {
272                                        value = "" + bd.doubleValue();
273                                }
274                        break;
275                        case Types.INTEGER:
276                        case Types.TINYINT:
277                        case Types.SMALLINT:
278                                int intValue = rs.getInt(colIndex);
279                                if (!rs.wasNull()) {
280                                        value = "" + intValue;
281                                }
282                        break;
283                        case Types.JAVA_OBJECT:
284                                Object obj = rs.getObject(colIndex);
285                                if (obj != null) {
286                                        value = String.valueOf(obj);
287                                }
288                        break;
289                        case Types.DATE:
290                                java.sql.Date date = rs.getDate(colIndex);
291                                if (date != null) {
292                                        value = DATE_FORMATTER.format(date);;
293                                }
294                        break;
295                        case Types.TIME:
296                                Time t = rs.getTime(colIndex);
297                                if (t != null) {
298                                        value = t.toString();
299                                }
300                        break;
301                        case Types.TIMESTAMP:
302                                Timestamp tstamp = rs.getTimestamp(colIndex);
303                                if (tstamp != null) {
304                                        value = TIMESTAMP_FORMATTER.format(tstamp);
305                                }
306                        break;
307                        case Types.LONGVARCHAR:
308                        case Types.VARCHAR:
309                        case Types.CHAR:
310                                value = rs.getString(colIndex);
311                        break;
312                        default:
313                                value = "";
314                }
315
316                
317                if (value == null)
318                {
319                        value = "";
320                }
321                
322                return value;
323        
324    }
325
326        private static String read(Clob c) throws SQLException, IOException
327        {
328                StringBuffer sb = new StringBuffer( (int) c.length());
329                Reader r = c.getCharacterStream();
330                char[] cbuf = new char[2048];
331                int n = 0;
332                while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
333                        if (n > 0) {
334                                sb.append(cbuf, 0, n);
335                        }
336                }
337                return sb.toString();
338        }
339    
340    /**
341     * Writes the next line to the file.
342     *
343     * @param nextLine
344     *            a string array with each comma-separated element as a separate
345     *            entry.
346     */
347    public void writeNext(String[] nextLine) {
348        
349        if (nextLine == null)
350                return;
351        
352        StringBuffer sb = new StringBuffer();
353        for (int i = 0; i < nextLine.length; i++) {
354
355            if (i != 0) {
356                sb.append(separator);
357            }
358
359            String nextElement = nextLine[i];
360            if (nextElement == null)
361                continue;
362            if (quotechar !=  NO_QUOTE_CHARACTER)
363                sb.append(quotechar);
364            for (int j = 0; j < nextElement.length(); j++) {
365                char nextChar = nextElement.charAt(j);
366                if (escapechar != NO_ESCAPE_CHARACTER && nextChar == quotechar) {
367                        sb.append(escapechar).append(nextChar);
368                } else if (escapechar != NO_ESCAPE_CHARACTER && nextChar == escapechar) {
369                        sb.append(escapechar).append(nextChar);
370                } else {
371                    sb.append(nextChar);
372                }
373            }
374            if (quotechar != NO_QUOTE_CHARACTER)
375                sb.append(quotechar);
376        }
377        
378        sb.append(lineEnd);
379        pw.write(sb.toString());
380
381    }
382
383    /**
384     * Flush underlying stream to writer.
385     * 
386     * @throws IOException if bad things happen
387     */
388    public void flush() throws IOException {
389
390        pw.flush();
391
392    } 
393
394    /**
395     * Close the underlying stream writer flushing any buffered content.
396     *
397     * @throws IOException if bad things happen
398     *
399     */
400    public void close() throws IOException {
401        pw.flush();
402        pw.close();
403        rawWriter.close();
404    }
405
406}