KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sqlmagic > tinysql > dbfFileTable


1 /*
2  *
3  * Extension of tinySQLTable which manipulates dbf files.
4  *
5  * Copyright 1996 John Wiley & Sons, Inc.
6  * See the COPYING file for redistribution details.
7  *
8  * $Author: davis $
9  * $Date: 2004/12/18 21:29:47 $
10  * $Revision: 1.1 $
11  *
12  */

13 package com.sqlmagic.tinysql;
14
15 import java.util.*;
16 import java.lang.*;
17 import java.io.*;
18
19
20 /**
21 dBase read/write access <br>
22 @author Brian Jepson <bjepson@home.com>
23 @author Marcel Ruff <ruff@swand.lake.de> Added write access to dBase and JDK 2 support
24 @author Thomas Morgner <mgs@sherito.org> Added caching for the current read row. A row
25 is now read only once and substrings are generated by each call to GetCol. Incredibly
26 increased read speed when little memory is available and disks are slow.
27 */

28 public class dbfFileTable extends tinySQLTable
29 {
30    private String JavaDoc fullPath,fileName;
31    private DBFHeader dbfHeader = null; // the first 32 bytes of the dBase file
32
private RandomAccessFile ftbl; // access handle to the dBase file
33
public boolean fileOpen=false;
34    final static String JavaDoc dbfExtension = ".DBF";
35
36    // dBase III column info offsets (one for each column):
37
final static int FIELD_NAME_INDEX = 0; // 0-10 column name, ASCIIZ - null padded
38
final static int FIELD_TYPE_INDEX = 11; // 11-11
39
final static int IMU_INDEX = 12; // 12-15 (in memory use)
40
final static int FIELD_LENGTH_INDEX = 16; // 16-16 (max field length = 254)
41
final static int DECIMAL_COUNT_INDEX = 17; // 17-17
42
final static int FIELD_RESERVED_INDEX = 18; // 18-31
43

44 /*
45  * The header section ends with carriage return CR.
46  *
47  * The data records have fixed length (from LENGTH_OF_REC_INDEX)
48  * the field entries in a record have fixed length (from FIELD_LENGTH_INDEX)
49  * all number and dates are stored as ASCII characters
50  */

51    final static int IS_DELETED_INDEX = 0; // '*': is deleted
52
// ' ': is not deleted
53
final static char RECORD_IS_DELETED = '*';
54    final static char RECORD_IS_NOT_DELETED = ' ';
55 /*
56  * Current record
57  */

58    int currentRecordNumber = 0; // current record, starts with 1!
59
/*
60  * The cache holds a single row as string and is read only once,
61  * and discarded when the cursor moves
62  */

63    private String JavaDoc currentRowCache = null;
64 /*
65  * End of file flag
66  */

67    boolean eof = false;
68    final boolean debug=false;
69 /*
70  *
71  * Constructs a dbfFileTable. This is only called by getTable()
72  * in dbfFile.java.
73  *
74  * @param dDir data directory
75  * @param table_name the name of the table
76  *
77  */

78    dbfFileTable( String JavaDoc dDir, String JavaDoc table_name ) throws tinySQLException
79    {
80       int aliasAt;
81       aliasAt = table_name.indexOf("->");
82       if ( aliasAt > -1 )
83       {
84          table = table_name.substring(0,aliasAt);
85          tableAlias = table_name.substring(aliasAt + 2);
86       } else {
87          table = table_name;
88          tableAlias = table_name;
89       }
90 /*
91  * The full path to the file
92  */

93       fileName = table;
94       if (!fileName.toUpperCase().endsWith(dbfExtension) )
95         fileName = fileName + dbfExtension;
96       fullPath = dDir + File.separator + fileName;
97       if ( debug )
98          System.out.println("dbfFileTable: fileName=" + fileName + " table="
99          + table + " fullPath=" + fullPath);
100 /*
101  * Open the DBF file
102  */

103       column_info = open_dbf();
104    }
105 /*
106  * Check if the file is open.
107  */

108    public boolean isOpen()
109    {
110       return fileOpen;
111    }
112 /*
113  * Close method. Try not to call this until you are sure
114  * the object is about to go out of scope.
115  */

116    public void close() throws tinySQLException
117    {
118       try
119       {
120          if ( debug ) System.out.println("Closing " + toString());
121          ftbl.close();
122          fileOpen = false;
123       } catch (IOException e) {
124          throw new tinySQLException(e.getMessage());
125       }
126    }
127 /*
128  * Returns the size of a column
129  *
130  * @param column name of the column
131  * @see tinySQLTable#ColSize
132  */

133    public int ColSize(String JavaDoc colName) throws tinySQLException
134    {
135       tsColumn coldef = getColumn(colName);
136       return coldef.size;
137    }
138 /*
139  * Returns the number of rows in the table
140  */

141    public int GetRowCount()
142    {
143       return dbfHeader.numRecords;
144    }
145 /*
146  * Returns the decimal places for a column
147  */

148    public int ColDec(String JavaDoc colName) throws tinySQLException
149    {
150       tsColumn coldef = getColumn(colName);
151       return coldef.decimalPlaces;
152    }
153 /*
154  * Returns the datatype of a column.
155  *
156  * @param column name of the column.
157  * @see tinySQLTable#ColType
158  *
159  * @changed to return java.sql.Types
160  */

161    public int ColType(String JavaDoc colName) throws tinySQLException
162    {
163       tsColumn coldef = getColumn(colName);
164       return coldef.type;
165    }
166 /*
167  * Get a column object for the named column.
168  */

169    public tsColumn getColumn(String JavaDoc colName) throws tinySQLException
170    {
171      int foundDot;
172      String JavaDoc columnName;
173      columnName = colName;
174      foundDot = columnName.indexOf(".");
175      if ( foundDot > -1 )
176         columnName = columnName.substring(foundDot+1);
177      tsColumn coldef = (tsColumn) column_info.get(columnName);
178      if ( coldef == (tsColumn)null )
179         throw new tinySQLException("Column " + columnName + " does not"
180         + " exist in table " + table);
181      return coldef;
182    }
183 /*
184  * Updates the current row in the table.
185  *
186  * @param c Ordered Vector of column names
187  * @param v Ordered Vector (must match order of c) of values
188  * @see tinySQLTable#UpdateCurrentRow
189  */

190    public void UpdateCurrentRow(Vector c, Vector v) throws tinySQLException
191    {
192 /*
193  * The Vectors v and c are expected to have the
194  * same number of elements. It is also expected
195  * that the elements correspond to each other,
196  * such that value 1 of Vector v corresponds to
197  * column 1 of Vector c, and so forth.
198  */

199       for (int i = 0; i < v.size(); i++)
200       {
201 /*
202  * Get the column name and the value, and
203  * invoke UpdateCol() to update it.
204  */

205          String JavaDoc column = ((String JavaDoc)c.elementAt(i)).toUpperCase();
206          String JavaDoc value = (String JavaDoc)v.elementAt(i);
207          UpdateCol(column, value);
208       }
209    }
210 /*
211  * Position the record pointer at the top of the table.
212  *
213  * @see tinySQLTable#GoTop
214  */

215    public void GoTop() throws tinySQLException
216    {
217       currentRowCache = null;
218       currentRecordNumber = 0;
219       eof = false;
220    }
221 /*
222  * Advance the record pointer to the next record.
223  *
224  * @see tinySQLTable#NextRecord
225  */

226    public boolean NextRecord() throws tinySQLException
227    {
228       currentRowCache = null;
229       if (currentRecordNumber < dbfHeader.numRecords)
230       {
231          currentRecordNumber++;
232          eof = false;
233          return true;
234       } else {
235          eof = true;
236          return false;
237       }
238    }
239 /*
240  * Insert a row. If c or v == null, insert a blank row
241  *
242  * @param c Ordered Vector of column names
243  * @param v Ordered Vector (must match order of c) of values
244  * @see tinySQLTable#InsertRow()
245  *
246  */

247    public void InsertRow(Vector c, Vector v) throws tinySQLException
248    {
249       try
250       {
251 /*
252  * Go to the end of the file, then write out the not deleted indicator
253  */

254          ftbl.seek( ftbl.length() );
255          ftbl.write(RECORD_IS_NOT_DELETED);
256 /*
257  * Write out a blank record
258  */

259          for (int i = 1; i < dbfHeader.recordLength; i++)
260          {
261             ftbl.write(' ');
262          }
263          int numRec = (int)dbfHeader.numRecords + 1;
264          currentRecordNumber = numRec;
265          dbfHeader.setNumRecords(ftbl, numRec);
266       } catch (Exception JavaDoc e) {
267          if ( debug ) e.printStackTrace();
268          throw new tinySQLException(e.getMessage());
269       }
270       if (c != null && v != null)
271          UpdateCurrentRow(c, v);
272       else
273          dbfHeader.setTimestamp(ftbl);
274    }
275 /*
276  * Retrieve a column's string value from the current row.
277  *
278  * @param column the column name
279  * @see tinySQLTable#GetCol
280  */

281    public String JavaDoc GetCol(String JavaDoc colName) throws tinySQLException
282    {
283       int foundDot;
284       String JavaDoc columnName;
285       columnName = colName;
286       foundDot = columnName.indexOf(".");
287       if ( foundDot > -1 )
288          columnName = columnName.substring(foundDot + 1);
289       tsColumn coldef = (tsColumn) column_info.get(columnName);
290       if (currentRowCache == null)
291         currentRowCache = _GetCol(ftbl, dbfHeader, currentRecordNumber);
292
293       return getColumn (coldef, currentRowCache);
294    }
295 /*
296  * Extracts a column from the given row. The row is given as a string.
297  * If coldef is null, the special delete-flag is returned (Position 0 of a row).
298  *
299  * @param coldef the column definition, which tells what content to extract from the row
300  * @param row the row as an string contains all column data
301  * @returns a substring of row.
302  */

303    public static String JavaDoc getColumn (tsColumn coldef, String JavaDoc row)
304    {
305       if (coldef == null)
306          return row.substring (0,1);
307       return row.substring(coldef.position, coldef.position + coldef.size);
308    }
309 /*
310  * Retrieve a column's string value from the given row and given colName
311  * @param ff the file handle
312  * @param colName the column name
313  * @param the wanted record (starts with 1)
314  * @see tinySQLTable#GetCol
315  *
316  * @author Thomas Morgner <mgs@sherito.org> This function retrieves a
317  * row, perhaps the name should changed to reflect the new function.
318  */

319    public static String JavaDoc _GetCol(RandomAccessFile ff, DBFHeader dbfHeader,
320       int currentRow) throws tinySQLException
321    {
322       try
323       {
324 /*
325  * Seek the starting offset of the current record,
326  * as indicated by currentRow
327  */

328          ff.seek(dbfHeader.headerLength + (currentRow - 1) * dbfHeader.recordLength);
329 /*
330  * Fully read a byte array out to the length of the record and convert
331  * it into a String.
332  */

333          byte[] b = new byte[dbfHeader.recordLength];
334          ff.readFully(b);
335          return new String JavaDoc(b, Utils.encode); // "Cp437"
336
} catch (Exception JavaDoc e) {
337          throw new tinySQLException(e.getMessage());
338       }
339    }
340 /*
341  * Update a single column.
342  *
343  * @param column the column name
344  * @param value the String value with which update the column
345  * @see tinySQLTable#UpdateCol
346  *
347  */

348    public void UpdateCol( String JavaDoc colName, String JavaDoc value ) throws tinySQLException
349    {
350       try
351       {
352 /*
353  * If it's the pseudo column _DELETED, return
354  */

355          if (colName.equals("_DELETED")) return;
356 /*
357  * Retrieve the tsColumn object which corresponds to this column.
358  */

359          tsColumn column = (tsColumn) column_info.get(colName);
360          if (column == null)
361             throw new tinySQLException("Can't update field=" + colName);
362 /*
363  * Seek the starting offset of the current record,
364  * as indicated by currentRecordNumber
365  */

366          ftbl.seek(dbfHeader.headerLength + (currentRecordNumber - 1) * dbfHeader.recordLength + column.position);
367 /*
368  * Enforce the correct column length, transform to byte and write to file
369  */

370          value = Utils.forceToSize(value, column.size, " ");
371          byte[] b = value.getBytes(Utils.encode);
372          ftbl.write(b);
373          dbfHeader.setTimestamp(ftbl);
374       } catch (Exception JavaDoc e) {
375          throw new tinySQLException(e.getMessage());
376       }
377    }
378 /*
379  * Delete the current row.
380  *
381  * @see tinySQLTable#DeleteRow
382  *
383  */

384    public void DeleteRow() throws tinySQLException
385    {
386       try
387       {
388          ftbl.seek(dbfHeader.headerLength + (currentRecordNumber - 1) * dbfHeader.recordLength);
389          ftbl.write(RECORD_IS_DELETED);
390       } catch (Exception JavaDoc e) {
391          throw new tinySQLException(e.getMessage());
392       }
393    }
394 /*
395  * Is the current row deleted?
396  *
397  * @see tinySQLTable#isDeleted()
398  */

399    public boolean isDeleted() throws tinySQLException
400    {
401       return ((GetCol("_DELETED")).charAt(0) == RECORD_IS_DELETED); // "*";
402
}
403 /*
404  * Checks whether the row is deleted.
405  */

406    public static boolean isDeleted(RandomAccessFile ff, DBFHeader dbfHeader, int currentRow) throws tinySQLException
407    {
408       char del = _GetCol(ff, dbfHeader, currentRow).charAt(0); // "_DELETED"
409
return del == RECORD_IS_DELETED;
410    }
411 /*
412  * Check if record is marked as deleted
413  * @param record the record string (the first byte '*' marks a deleted record)
414  */

415    public static boolean isDeleted(String JavaDoc record)
416    {
417       if (record.charAt(IS_DELETED_INDEX) == RECORD_IS_DELETED)
418          return true; // '*'
419
return false; // ' '
420
}
421 /***************************************************************************
422  *
423  * End methods implemented from tinySQLTable.java
424  * the rest of this stuff is private methods
425  * for dbfFileTable.
426  *
427  * @return Length in bytes of one row or 0 if not known
428  */

429    public int getRecordLength()
430    {
431       return dbfHeader.recordLength;
432    }
433    public String JavaDoc toString()
434    {
435       StringBuffer JavaDoc outputBuffer;
436       outputBuffer = new StringBuffer JavaDoc();
437       outputBuffer.append("Table " + table + ", path " + fullPath
438       + ", file " + ftbl.toString());
439       return outputBuffer.toString();
440    }
441 /*
442  * opens a DBF file. This is based on Pratap Pereira's
443  * Xbase.pm perl module
444  * @return column definition list (HashTable)
445  *
446  * @author Thomas Morgner <mgs@sherito.org> added check for
447  * file exists, before the file is opened. Opening a non existing
448  * file will create a new file, and we get errors while trying
449  * to read the non-existend headers
450  */

451    Hashtable open_dbf() throws tinySQLException
452    {
453       try
454       {
455          File f = new File (fullPath);
456          if (debug) System.out.println("Try to open " + f.getAbsolutePath());
457          if (!f.exists() )
458          {
459             throw new tinySQLException ("Unable to open " + f.getAbsolutePath()
460             + " - does not exist. or can't be read.");
461          } else if (!f.canRead () ) {
462             throw new tinySQLException ("Unable to open " + f.getAbsolutePath()
463             + " - file can't be read (permissions?).");
464          }
465          if (f.canWrite ())
466          {
467             ftbl = new RandomAccessFile(f, "rw");
468          } else {
469 /*
470  * Open readonly if the file is not writeable. Needed for
471  * databases on CD-Rom
472  */

473             ftbl = new RandomAccessFile(f, "r");
474          }
475 /*
476  * Read the first 32 bytes ...
477  */

478          dbfHeader = new DBFHeader(ftbl);
479 /*
480  * read the column info (each is a 32 byte bulk) ...
481  */

482          Hashtable coldef_list = new Hashtable();
483          int locn = 0; // offset of the current column
484
for (int i = 1; i <= dbfHeader.numFields; i++)
485          {
486             tsColumn coldef = dbfFile.readColdef(ftbl, table, i, locn);
487             locn += coldef.size; // increment locn by the length of this field.
488
coldef_list.put(coldef.name, coldef);
489          }
490          fileOpen = true;
491          return coldef_list;
492       } catch (Exception JavaDoc e) {
493          if ( debug ) e.printStackTrace();
494          throw new tinySQLException(e.getMessage());
495       }
496    }
497 }
498
Popular Tags