View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Part of Melati (http://melati.org), a framework for the rapid
6    * development of clean, maintainable web applications.
7    *
8    *  Copyright (C) 2001 Myles Chippendale
9    *
10   * Melati is free software; Permission is granted to copy, distribute
11   * and/or modify this software under the terms either:
12   *
13   * a) the GNU General Public License as published by the Free Software
14   *    Foundation; either version 2 of the License, or (at your option)
15   *    any later version,
16   *
17   *    or
18   *
19   * b) any version of the Melati Software License, as published
20   *    at http://melati.org
21   *
22   * You should have received a copy of the GNU General Public License and
23   * the Melati Software License along with this program;
24   * if not, write to the Free Software Foundation, Inc.,
25   * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26   * GNU General Public License and visit http://melati.org to obtain the
27   * Melati Software License.
28   *
29   * Feel free to contact the Developers of Melati (http://melati.org),
30   * if you would like to work out a different arrangement than the options
31   * outlined here.  It is our intention to allow Melati to be used by as
32   * wide an audience as possible.
33   *
34   * This program is distributed in the hope that it will be useful,
35   * but WITHOUT ANY WARRANTY; without even the implied warranty of
36   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37   * GNU General Public License for more details.
38   *
39   * Contact details for copyright holder:
40   *
41   *     Myles Chippendale <mylesc At paneris.org>
42   *
43   *
44   * ------
45   *  Note
46   * ------
47   *
48   * I will assign copyright to PanEris (http://paneris.org) as soon as
49   * we have sorted out what sort of legal existence we need to have for
50   * that to make sense. 
51   * In the meantime, if you want to use Melati on non-GPL terms,
52   * contact me!
53   */
54  
55  package org.melati.poem.csv;
56  
57  import java.io.BufferedReader;
58  import java.io.File;
59  import java.io.FileReader;
60  import java.io.IOException;
61  import java.io.Writer;
62  import java.util.Enumeration;
63  import java.util.Hashtable;
64  import java.util.NoSuchElementException;
65  import java.util.Vector;
66  
67  import org.melati.poem.Persistent;
68  import org.melati.poem.Table;
69  
70  /**
71   * A representation of a CSV file as a POEM Table.
72   */
73  public class CSVTable {
74  
75    protected Table<?> table = null;
76    protected File data = null;
77    protected Hashtable<String,CSVColumn> columns = new Hashtable<String,CSVColumn>();
78    protected Vector<CSVColumn> columnsInUploadOrder = new Vector<CSVColumn>();
79    protected CSVColumn primaryKey = null;
80    protected Vector<CSVRecord> records = new Vector<CSVRecord>();
81    protected BufferedReader reader = null;
82    protected CSVFileParser parser = null;
83    
84    /** The line number of the CSV file. */
85    private int lineNo;
86    
87    /** The record number of the CSV file. */
88    private int recordNo;
89  
90  
91   /**
92    * Constructor.
93    * 
94    * @param table POEM table to load data into
95    * @param data CSV file to read from
96    */
97    public CSVTable(Table<?> table, File data) {
98      this.table = table;
99      this.data = data;
100     try {
101       reader = new BufferedReader(new FileReader(this.data));
102       parser = new CSVFileParser(this.reader);
103     } catch (Exception e) {
104       throw new RuntimeException(e);
105     }
106   }
107 
108   /**
109    * Process the first line to define columns. 
110    * The first line contains the field names - this needs to be
111    * validated against expected values, and the order of
112    * the fields established.
113    * 
114    * @throws IOException
115    */
116   public void define() throws IOException  {
117       parser.nextRecord();
118       lineNo = 1;
119       recordNo = 0;
120 
121       while (parser.recordHasMoreFields()) {
122         String key = parser.nextField();
123         CSVColumn col = columns.get(key);
124         if (col == null)
125           throw new CSVParseException(
126             "I don't know what to do with the column in " + data.getPath() +
127             " called " + key); 
128         columnsInUploadOrder.addElement(col);
129       }
130   }  
131 
132  /**
133   * Add column definitions to this table.
134   */
135   public void addColumn(String csvName, String poemName) {
136     columns.put(csvName, new CSVColumn(poemName));
137   }
138 
139  /**
140   * Add column definitions, perhaps Primary Keys, to this table.
141   */
142   public void addColumn(String csvName, String poemName, boolean isPrimaryKey)
143       throws CSVPrimaryKeyColumnAlreadySetException {
144     if (isPrimaryKey && primaryKey != null)
145       throw new CSVPrimaryKeyColumnAlreadySetException(table.getName());
146     CSVColumn col = new CSVColumn(poemName, isPrimaryKey);
147     columns.put(csvName, col);
148     if (isPrimaryKey)
149       primaryKey = col;
150   }
151 
152    /**
153     * Add column definitions for foreign keys to this table.
154     */
155     public void addColumn(String csvName,  
156                           String foreignPoemName, CSVTable foreignTable) {
157       columns.put(csvName, new CSVColumn(foreignPoemName, foreignTable));
158     }
159 
160 
161  /**
162   * Parse the CSV data file and store the data for saving later.
163   * 
164   * @param writeOnFly whether to commit each line to db as we go
165   * @throws IOException if there is a file system problem
166   * @throws CSVParseException if the is a malformed field in the CSV
167   * @throws CSVWriteDownException
168   * @throws NoPrimaryKeyInCSVTableException
169   */
170   public void load(boolean writeOnFly) 
171       throws IOException, CSVParseException, NoPrimaryKeyInCSVTableException, 
172              CSVWriteDownException {
173 
174     try {
175       define();
176       // Suck in all the data
177       CSVRecord record;
178       while(null != (record = parseRecord())) {
179         record.setLineNo(lineNo++);
180         record.setRecordNo(recordNo++);
181         if (writeOnFly)
182           record.makePersistent();
183         else 
184           records.addElement(record);
185       }
186 
187     }
188     catch (IllegalArgumentException e) {
189        throw new CSVParseException("Failed to load field in " +
190                                     data.getPath() + 
191                                     " line " + lineNo +
192                                     ": " + e.toString());
193     }
194     catch (NoSuchElementException f) {
195        throw new CSVParseException("Failed to read column header in " +
196                                     data.getPath() + 
197                                     " line " + lineNo +
198                                     ": " + f.toString());
199     }
200     finally {
201       reader.close();
202     }
203   }
204 
205 
206  /**
207   * Reads the file until is has seen an object's-worth of
208   * field values (ie until it sees an EOF or a line starting
209   * with '$') which it returns in a hashtable (null if there are
210   * no field values).
211    * @return a new CSVRecord
212    * @throws IOException if there is a problem with the file system
213    * @throws CSVParseException if there is a problem parsing the input
214    */
215   public CSVRecord parseRecord() throws IOException, CSVParseException {
216     if (!parser.nextRecord())
217       return null;
218 
219     int i = 0;
220     String value = null;
221     try {
222       CSVRecord record = new CSVRecord(table);
223       for (; i < columnsInUploadOrder.size(); i++) {
224         value = parser.nextField();
225         CSVColumn col = (CSVColumn)columnsInUploadOrder.elementAt(i);
226         record.addField(new CSVField(col, value));
227       }
228       record.setLineNo(parser.getLineNo());
229       return record;
230     }
231     catch (IllegalArgumentException e) {
232        throw new CSVParseException("Failed to read data field no. " + 
233                                     (i+1) + 
234                                     " in " +
235                                     data + 
236                                     " line " + lineNo +
237                                     ": " + e.toString());
238     }
239     catch (NoSuchElementException f) {
240       String message = "Problem with data field no. " + (i+1) +
241       " of " + columnsInUploadOrder.size() +
242       " in " + data +
243       " line " + lineNo;
244       
245       if (value == null) {
246         message += " (Check last line of file) : " + 
247                     f.toString();
248       } else {
249         message += ", Value:" + value + ": " + f.toString();
250       }
251       throw new CSVParseException(message);
252     }
253   }
254 
255 
256  /**
257   * Delete all Persistents from the Poem table.
258   */
259   public void emptyTable() {
260     Enumeration<?> rows = table.selection();
261     while(rows.hasMoreElements()) {
262       Persistent p = (Persistent)rows.nextElement();
263       p.delete();
264     }
265   }
266 
267  /**
268   * Write the records to the database, 
269   * called if we are not writing each record to db as we go.
270   */
271   public void writeRecords() 
272       throws NoPrimaryKeyInCSVTableException,  CSVWriteDownException {
273     for (int i = 0; i < records.size(); i++) {
274       ((CSVRecord)records.elementAt(i)).makePersistent();
275     }
276   }
277   
278 
279   /**
280    * Lookup the Persistent corresponding to the CSV record
281    * with the given value for the CSV table's primary key.
282    * @throws NoPrimaryKeyInCSVTableException
283    * @throws CSVWriteDownException
284    */
285   protected Persistent getRecordWithID(String csvValue)
286       throws NoPrimaryKeyInCSVTableException, CSVWriteDownException {
287     if (primaryKey == null)
288       throw new NoPrimaryKeyInCSVTableException(table.getName(), csvValue);
289 
290     for (int i = 0; i < records.size(); i++) {
291       CSVRecord record = (CSVRecord)records.elementAt(i);
292       if (record.primaryKeyValue != null &&
293           record.primaryKeyValue.equals(csvValue))
294         return record.getPersistent();
295     }
296     return null;
297   }
298 
299  /**
300   * Return a string reporting on the data added to this table.
301   */
302   public void report(boolean recordDetails, boolean fieldDetails,
303                        Writer output) throws IOException {
304 
305     output.write("*** TABLE: " + table.getName().toUpperCase() + " **\n\n");
306     output.write("** I have read " + records.size() + " records of " +
307                    columnsInUploadOrder.size() + " fields\n");
308 
309     if (recordDetails) {
310       for (int i = 0; i < records.size(); i++) {
311         CSVRecord record = (CSVRecord)records.elementAt(i);
312         output.write("   Record: CSV primary key = " +
313                         record.primaryKeyValue);
314 
315         if (record.poemRecord == null)
316           output.write(", No Poem Persistent written\n");
317         else
318           output.write(", Poem Troid = " + record.poemRecord.getTroid() + "\n");
319 
320         if (fieldDetails) {
321           for (int j = 0; j < record.getFields().size(); j++) {
322             CSVField field = (CSVField)record.getFields().elementAt(j);
323             output.write(field.column + "=\"" + field.value);
324             if (j < record.getFields().size()-1)
325               output.write("\",");
326             else
327               output.write("\"\n");
328           }
329         }
330       }
331     }
332     output.write("** Currently " + table.count(null) +
333                   " Persistents in this table\n\n");
334   }
335 
336   /**
337    * Used in debugging to display name of table being emptied.
338    * 
339    * @return the POEM Table's name
340    */
341   public String getName() {
342     return table.getName();
343   }
344 }
345