KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > sf > jasperreports > engine > data > JRCsvDataSource


1 /*
2  * ============================================================================
3  * GNU Lesser General Public License
4  * ============================================================================
5  *
6  * JasperReports - Free Java report-generating library.
7  * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22  *
23  * JasperSoft Corporation
24  * 303 Second Street, Suite 450 North
25  * San Francisco, CA 94107
26  * http://www.jaspersoft.com
27  */

28 package net.sf.jasperreports.engine.data;
29
30 import java.io.BufferedReader JavaDoc;
31 import java.io.File JavaDoc;
32 import java.io.FileInputStream JavaDoc;
33 import java.io.FileNotFoundException JavaDoc;
34 import java.io.IOException JavaDoc;
35 import java.io.InputStream JavaDoc;
36 import java.io.InputStreamReader JavaDoc;
37 import java.io.Reader JavaDoc;
38 import java.io.UnsupportedEncodingException JavaDoc;
39 import java.math.BigDecimal JavaDoc;
40 import java.math.BigInteger JavaDoc;
41 import java.text.DateFormat JavaDoc;
42 import java.text.DecimalFormat JavaDoc;
43 import java.text.NumberFormat JavaDoc;
44 import java.text.SimpleDateFormat JavaDoc;
45 import java.util.HashMap JavaDoc;
46 import java.util.Vector JavaDoc;
47
48 import net.sf.jasperreports.engine.JRDataSource;
49 import net.sf.jasperreports.engine.JRException;
50 import net.sf.jasperreports.engine.JRField;
51 import net.sf.jasperreports.engine.JRRuntimeException;
52
53
54 /**
55  * This datasource implementation reads a CSV stream. Datasource rows are separated by a record delimiter string and
56  * fields inside a row are separated by a field delimiter character. Fields containing delimiter characters can be
57  * placed inside quotes. If fields contain quotes themselves, these are duplicated (example: <i>"John ""Doe"""<i> will be
58  * displayed as <i>John "Doe"</i>).
59  * <p>
60  * Since CSV does not specify column names, the default naming convention is to name report fields COLUMN_x and map each
61  * column with the field found at index x in each row (these indices start with 0). To avoid this situation, users can
62  * either specify a collection of column names or set a flag to read the column names from the first row of the CSV file.
63  *
64  * @author Ionut Nedelcu (ionutned@users.sourceforge.net)
65  * @version $Id: JRCsvDataSource.java 1529 2006-12-21 16:05:08 +0200 (Thu, 21 Dec 2006) teodord $
66  */

67 public class JRCsvDataSource implements JRDataSource
68 {
69     private DateFormat JavaDoc dateFormat = new SimpleDateFormat JavaDoc();
70     private NumberFormat JavaDoc numberFormat = new DecimalFormat JavaDoc();
71     private char fieldDelimiter = ',';
72     private String JavaDoc recordDelimiter = "\n";
73     private HashMap JavaDoc columnNames = new HashMap JavaDoc();
74     private boolean useFirstRowAsHeader;
75
76     private Vector JavaDoc fields;
77     private Reader JavaDoc reader;
78     private char buffer[] = new char[1024];
79     private int position;
80     private int bufSize;
81     private boolean processingStarted;
82
83
84     /**
85      * Creates a datasource instance from a CSV data input stream, using the default encoding.
86      * @param stream an input stream containing CSV data
87      */

88     public JRCsvDataSource(InputStream JavaDoc stream)
89     {
90         this(new BufferedReader JavaDoc(new InputStreamReader JavaDoc(stream)));
91     }
92
93
94     /**
95      * Creates a datasource instance from a CSV data input stream, using the specified encoding.
96      * @param stream an input stream containing CSV data
97      * @param charsetName the encoding to use
98      */

99     public JRCsvDataSource(InputStream JavaDoc stream, String JavaDoc charsetName) throws UnsupportedEncodingException JavaDoc
100     {
101         this(new BufferedReader JavaDoc(new InputStreamReader JavaDoc(stream, charsetName)));
102     }
103
104
105     /**
106      * Creates a datasource instance from a CSV file, using the default encoding.
107      * @param file a file containing CSV data
108      */

109     public JRCsvDataSource(File JavaDoc file) throws FileNotFoundException JavaDoc
110     {
111         this(new FileInputStream JavaDoc(file));
112     }
113
114
115     /**
116      * Creates a datasource instance from a CSV file, using the specified encoding.
117      * @param file a file containing CSV data
118      * @param charsetName the encoding to use
119      */

120     public JRCsvDataSource(File JavaDoc file, String JavaDoc charsetName) throws FileNotFoundException JavaDoc, UnsupportedEncodingException JavaDoc
121     {
122         this(new FileInputStream JavaDoc(file), charsetName);
123     }
124
125
126     /**
127      * Creates a datasource instance from a CSV data reader.
128      * @param reader a <tt>Reader</tt> instance, for reading the stream
129      */

130     public JRCsvDataSource(Reader JavaDoc reader)
131     {
132         this.reader = reader;
133     }
134
135
136     /**
137      *
138      */

139     public boolean next() throws JRException
140     {
141         try {
142             if (!processingStarted) {
143                 if (useFirstRowAsHeader) {
144                     parseRow();
145                     for (int i = 0; i < fields.size(); i++) {
146                         String JavaDoc name = (String JavaDoc) fields.get(i);
147                         this.columnNames.put(name, new Integer JavaDoc(i));
148                     }
149                 }
150                 processingStarted = true;
151             }
152
153             return parseRow();
154         } catch (IOException JavaDoc e) {
155             throw new JRException(e);
156         }
157     }
158
159
160     /**
161      *
162      */

163     public Object JavaDoc getFieldValue(JRField jrField) throws JRException
164     {
165         String JavaDoc fieldName = jrField.getName();
166
167         Integer JavaDoc columnIndex = (Integer JavaDoc) columnNames.get(fieldName);
168         if (columnIndex == null && fieldName.startsWith("COLUMN_")) {
169             columnIndex = Integer.valueOf(fieldName.substring(7));
170         }
171         if (columnIndex == null)
172             throw new JRException("Unknown column name : " + fieldName);
173
174         if (fields.size() > columnIndex.intValue()) {
175             String JavaDoc fieldValue = (String JavaDoc) fields.get(columnIndex.intValue());
176             Class JavaDoc valueClass = jrField.getValueClass();
177             
178             if (valueClass.equals(String JavaDoc.class))
179                 return fieldValue;
180
181             fieldValue = fieldValue.trim();
182             
183             if (fieldValue.length() == 0)
184                 return null;
185             
186             try {
187                 if (valueClass.equals(Boolean JavaDoc.class)) {
188                     return fieldValue.equalsIgnoreCase("true") ? Boolean.TRUE : Boolean.FALSE;
189                 }
190                 else if (valueClass.equals(Byte JavaDoc.class)) {
191                     return new Byte JavaDoc((numberFormat.parse(fieldValue)).byteValue());
192                 }
193                 else if (valueClass.equals(Integer JavaDoc.class)) {
194                     return new Integer JavaDoc((numberFormat.parse(fieldValue)).intValue());
195                 }
196                 else if (valueClass.equals(Long JavaDoc.class)) {
197                     return new Long JavaDoc((numberFormat.parse(fieldValue)).longValue());
198                 }
199                 else if (valueClass.equals(Short JavaDoc.class)) {
200                     return new Short JavaDoc((numberFormat.parse(fieldValue)).shortValue());
201                 }
202                 else if (valueClass.equals(Double JavaDoc.class)) {
203                     return new Double JavaDoc((numberFormat.parse(fieldValue)).doubleValue());
204                 }
205                 else if (valueClass.equals(Float JavaDoc.class)) {
206                     return new Float JavaDoc((numberFormat.parse(fieldValue)).floatValue());
207                 }
208                 else if (valueClass.equals(BigDecimal JavaDoc.class)) {
209                     return new BigDecimal JavaDoc((numberFormat.parse(fieldValue)).toString());
210                 }
211                 else if (valueClass.equals(BigInteger JavaDoc.class)) {
212                     return new BigInteger JavaDoc(String.valueOf(numberFormat.parse(fieldValue).longValue()));
213                 }
214                 else if(valueClass.equals(java.lang.Number JavaDoc.class)) {
215                     return numberFormat.parse(fieldValue);
216                 }
217                 else if (valueClass.equals(java.util.Date JavaDoc.class)) {
218                     return dateFormat.parse(fieldValue);
219                 }
220                 else if (valueClass.equals(java.sql.Timestamp JavaDoc.class)) {
221                     return new java.sql.Timestamp JavaDoc(dateFormat.parse(fieldValue).getTime());
222                 }
223                 else if (valueClass.equals(java.sql.Time JavaDoc.class)) {
224                     return new java.sql.Time JavaDoc(dateFormat.parse(fieldValue).getTime());
225                 }
226                 else
227                     throw new JRException("Field '" + jrField.getName() + "' is of class '" + valueClass.getName() + "' and can not be converted");
228             } catch (Exception JavaDoc e) {
229                 throw new JRException("Unable to get value for field '" + jrField.getName() + "' of class '" + valueClass.getName() + "'", e);
230             }
231         }
232
233         throw new JRException("Unknown column name : " + fieldName);
234     }
235
236
237     /**
238      * Parses a row of CSV data and extracts the fields it contains
239      */

240     private boolean parseRow() throws IOException JavaDoc
241     {
242         int pos = 0;
243         int startFieldPos = 0;
244         boolean insideQuotes = false;
245         boolean hadQuotes = false;
246         boolean misplacedQuote = false;
247         char c;
248         fields = new Vector JavaDoc();
249
250         String JavaDoc row = getRow();
251         if (row == null || row.length() == 0)
252             return false;
253
254         while (pos < row.length()) {
255             c = row.charAt(pos);
256
257             if (c == '"') {
258                 // already inside a text containing quotes
259
if (!insideQuotes) {
260                     if (!hadQuotes) {
261                         insideQuotes = true;
262                         hadQuotes = true;
263                     }
264                     else // the field contains a bad string, like "fo"o", instead of "fo""o"
265
misplacedQuote = true;
266                 }
267                 // found a quote when already inside quotes, expecting two consecutive quotes, otherwise it means
268
// it's a closing quote
269
else {
270                     if (pos+1 < row.length() && row.charAt(pos+1) == '"')
271                         pos++;
272                     else
273                         insideQuotes = false;
274                 }
275             }
276             // field delimiter found, copy the field contents to the field array
277
if (c == fieldDelimiter && !insideQuotes) {
278                 String JavaDoc field = row.substring(startFieldPos, pos);
279                 // if an illegal quote was found, the entire field is considered illegal
280
if (misplacedQuote) {
281                     misplacedQuote = false;
282                     hadQuotes = false;
283                     field = "";
284                 }
285                 // if the field was between quotes, remove them and turn any escaped quotes inside the text into normal quotes
286
else if (hadQuotes) {
287                     field = field.trim();
288                     if (field.startsWith("\"") && field.endsWith("\"")) {
289                         field = field.substring(1, field.length() - 1);
290                         field = replaceAll(field, "\"\"", "\"");
291                     }
292                     else
293                         field = "";
294                     hadQuotes = false;
295                 }
296
297                 fields.add(field);
298                 startFieldPos = pos + 1;
299             }
300
301             pos++;
302             // if the record delimiter was found inside a quoted field, it is not an actual record delimiter,
303
// so another line should be read
304
if ((pos == row.length()) && insideQuotes) {
305                 row = row + recordDelimiter + getRow();
306             }
307         }
308
309         // end of row was reached, so the final characters form the last field in the record
310
String JavaDoc field = row.substring(startFieldPos, pos);
311         if (field == null)
312             return true;
313
314         if (misplacedQuote)
315             field = "";
316         else if (hadQuotes) {
317             field = field.trim();
318             if (field.startsWith("\"") && field.endsWith("\"")) {
319                 field = field.substring(1, field.length() - 1);
320                 field = replaceAll(field, "\"\"", "\"");
321             }
322             else
323                 field = "";
324         }
325         fields.add(field);
326
327         return true;
328     }
329
330
331     /**
332      * Reads a row from the stream. A row is a sequence of characters separated by the record delimiter.
333      */

334     private String JavaDoc getRow() throws IOException JavaDoc
335     {
336         StringBuffer JavaDoc row = new StringBuffer JavaDoc();
337         char c;
338
339         while (true) {
340             try {
341                 c = getChar();
342
343                 // searches for the first character of the record delimiter
344
if (c == recordDelimiter.charAt(0)) {
345                     int i;
346                     char[] temp = new char[recordDelimiter.length()];
347                     temp[0] = c;
348                     boolean isDelimiter = true;
349                     // checks if the following characters in the stream form the record delimiter
350
for (i = 1; i < recordDelimiter.length() && isDelimiter; i++) {
351                         temp[i] = getChar();
352                         if (temp[i] != recordDelimiter.charAt(i))
353                             isDelimiter = false;
354                     }
355
356                     if (isDelimiter)
357                         return row.toString();
358
359                     row.append(temp, 0, i);
360                 }
361
362                 row.append(c);
363             } catch (JRException e) {
364                 return row.toString();
365             }
366
367         } // end while
368
}
369
370
371     /**
372      * Reads a character from the stream.
373      * @throws IOException if any I/O error occurs
374      * @throws JRException if end of stream has been reached
375      */

376     private char getChar() throws IOException JavaDoc, JRException
377     {
378         // end of buffer, fill a new buffer
379
if (position + 1 > bufSize) {
380             bufSize = reader.read(buffer);
381             position = 0;
382             if (bufSize == -1)
383                 throw new JRException("No more chars");
384         }
385
386         return buffer[position++];
387     }
388
389
390     /**
391      * Gets the date format that will be used to parse date fields
392      */

393     public DateFormat JavaDoc getDateFormat()
394     {
395         return dateFormat;
396     }
397
398
399     /**
400      * Sets the desired date format to be used for parsing date fields
401      */

402     public void setDateFormat(DateFormat JavaDoc dateFormat)
403     {
404         if (processingStarted)
405             throw new JRRuntimeException("Cannot modify data source properties after data reading has started");
406         this.dateFormat = dateFormat;
407     }
408
409
410     /**
411      * Returns the field delimiter character.
412      */

413     public char getFieldDelimiter()
414     {
415         return fieldDelimiter;
416     }
417
418
419     /**
420      * Sets the field delimiter character. The default is comma. If characters such as comma or quotes are specified,
421      * the results can be unpredictable.
422      * @param fieldDelimiter
423      */

424     public void setFieldDelimiter(char fieldDelimiter)
425     {
426         if (processingStarted)
427             throw new JRRuntimeException("Cannot modify data source properties after data reading has started");
428         this.fieldDelimiter = fieldDelimiter;
429     }
430
431
432     /**
433      * Returns the record delimiter string.
434      */

435     public String JavaDoc getRecordDelimiter()
436     {
437         return recordDelimiter;
438     }
439
440
441     /**
442      * Sets the record delimiter string. The default is line feed (\n).
443      * @param recordDelimiter
444      */

445     public void setRecordDelimiter(String JavaDoc recordDelimiter)
446     {
447         if (processingStarted)
448             throw new JRRuntimeException("Cannot modify data source properties after data reading has started");
449         this.recordDelimiter = recordDelimiter;
450     }
451
452
453     /**
454      * Specifies an array of strings representing column names matching field names in the report template
455      */

456     public void setColumnNames(String JavaDoc[] columnNames)
457     {
458         if (processingStarted)
459             throw new JRRuntimeException("Cannot modify data source properties after data reading has started");
460         for (int i = 0; i < columnNames.length; i++)
461             this.columnNames.put(columnNames[i], new Integer JavaDoc(i));
462     }
463
464
465     /**
466      * Specifies whether the first line of the CSV file should be considered a table
467      * header, containing column names matching field names in the report template
468      */

469     public void setUseFirstRowAsHeader(boolean useFirstRowAsHeader)
470     {
471         if (processingStarted)
472             throw new JRRuntimeException("Cannot modify data source properties after data reading has started");
473         this.useFirstRowAsHeader = useFirstRowAsHeader;
474     }
475
476
477     /**
478      * Closes the reader. Users of this data source should close it after usage.
479      */

480     public void close()
481     {
482         try
483         {
484             reader.close();
485         }
486         catch(IOException JavaDoc e)
487         {
488             //nothing to do
489
}
490     }
491
492
493     private String JavaDoc replaceAll(String JavaDoc string, String JavaDoc substring, String JavaDoc replacement)
494     {
495         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
496         int index = string.indexOf(substring);
497         int oldIndex = 0;
498         while (index >= 0) {
499             result.append(string.substring(oldIndex, index));
500             result.append(replacement);
501             index += substring.length();
502             oldIndex = index;
503
504             index = string.indexOf(substring, index);
505         }
506
507         if (oldIndex < string.length())
508             result.append(string.substring(oldIndex, string.length()));
509
510         return result.toString();
511     }
512
513
514     public NumberFormat JavaDoc getNumberFormat() {
515         return numberFormat;
516     }
517
518
519     public void setNumberFormat(NumberFormat JavaDoc numberFormat) {
520         this.numberFormat = numberFormat;
521     }
522 }
523
524
525
Popular Tags