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}