/*
*
* Extension of tinySQLTable which manipulates dbf files.
*
* Copyright 1996 John Wiley & Sons, Inc.
* See the COPYING file for redistribution details.
*
* $Author: davis $
* $Date: 2004/12/18 21:29:47 $
* $Revision: 1.1 $
*
*/
package com.sqlmagic.tinysql;
import java.util.*;
import java.lang.*;
import java.io.*;
/**
dBase read/write access <br>
@author Brian Jepson <bjepson@home.com>
@author Marcel Ruff <ruff@swand.lake.de> Added write access to dBase and JDK 2 support
@author Thomas Morgner <mgs@sherito.org> Added caching for the current read row. A row
is now read only once and substrings are generated by each call to GetCol. Incredibly
increased read speed when little memory is available and disks are slow.
*/
public class dbfFileTable extends tinySQLTable
{
private String fullPath,fileName;
private DBFHeader dbfHeader = null; // the first 32 bytes of the dBase file
private RandomAccessFile ftbl; // access handle to the dBase file
public boolean fileOpen=false;
final static String dbfExtension = ".DBF";
// dBase III column info offsets (one for each column):
final static int FIELD_NAME_INDEX = 0; // 0-10 column name, ASCIIZ - null padded
final static int FIELD_TYPE_INDEX = 11; // 11-11
final static int IMU_INDEX = 12; // 12-15 (in memory use)
final static int FIELD_LENGTH_INDEX = 16; // 16-16 (max field length = 254)
final static int DECIMAL_COUNT_INDEX = 17; // 17-17
final static int FIELD_RESERVED_INDEX = 18; // 18-31
/*
* The header section ends with carriage return CR.
*
* The data records have fixed length (from LENGTH_OF_REC_INDEX)
* the field entries in a record have fixed length (from FIELD_LENGTH_INDEX)
* all number and dates are stored as ASCII characters
*/
final static int IS_DELETED_INDEX = 0; // '*': is deleted
// ' ': is not deleted
final static char RECORD_IS_DELETED = '*';
final static char RECORD_IS_NOT_DELETED = ' ';
/*
* Current record
*/
int currentRecordNumber = 0; // current record, starts with 1!
/*
* The cache holds a single row as string and is read only once,
* and discarded when the cursor moves
*/
private String currentRowCache = null;
/*
* End of file flag
*/
boolean eof = false;
/*
*
* Constructs a dbfFileTable. This is only called by getTable()
* in dbfFile.java.
*
* @param dDir data directory
* @param table_name the name of the table
*
*/
dbfFileTable( String dDir, String table_name ) throws tinySQLException
{
int aliasAt;
aliasAt = table_name.indexOf("->");
if ( aliasAt > -1 )
{
table = table_name.substring(0,aliasAt);
tableAlias = table_name.substring(aliasAt + 2);
} else {
table = table_name;
tableAlias = table_name;
}
/*
* The full path to the file
*/
fileName = table;
if (!fileName.toUpperCase().endsWith(dbfExtension) )
fileName = fileName + dbfExtension;
fullPath = dDir + File.separator + fileName;
if ( tinySQLGlobals.DEBUG )
System.out.println("dbfFileTable: fileName=" + fileName + "\nTable="
+ table + "\nfullPath=" + fullPath);
/*
* Open the DBF file
*/
column_info = open_dbf();
}
/*
* Check if the file is open.
*/
public boolean isOpen()
{
return fileOpen;
}
/*
* Close method. Try not to call this until you are sure
* the object is about to go out of scope.
*/
public void close() throws tinySQLException
{
try
{
if ( tinySQLGlobals.DEBUG )
System.out.println("Closing " + toString());
ftbl.close();
fileOpen = false;
} catch (IOException e) {
throw new tinySQLException(e.getMessage());
}
}
/*
* Returns the size of a column
*
* @param column name of the column
* @see tinySQLTable#ColSize
*/
public int ColSize(String colName) throws tinySQLException
{
tsColumn coldef = getColumn(colName);
return coldef.size;
}
/*
* Returns the number of rows in the table
*/
public int GetRowCount()
{
return dbfHeader.numRecords;
}
/*
* Returns the decimal places for a column
*/
public int ColDec(String colName) throws tinySQLException
{
tsColumn coldef = getColumn(colName);
return coldef.decimalPlaces;
}
/*
* Returns the datatype of a column.
*
* @param column name of the column.
* @see tinySQLTable#ColType
*
* @changed to return java.sql.Types
*/
public int ColType(String colName) throws tinySQLException
{
tsColumn coldef = getColumn(colName);
return coldef.type;
}
/*
* Get a column object for the named column.
*/
public tsColumn getColumn(String colName) throws tinySQLException
{
int foundDot;
String columnName;
columnName = colName;
foundDot = columnName.indexOf(".");
if ( foundDot > -1 )
columnName = columnName.substring(foundDot+1);
columnName = tinySQLGlobals.getShortName(columnName);
tsColumn coldef = (tsColumn) column_info.get(columnName);
if ( coldef == (tsColumn)null )
throw new tinySQLException("Column " + columnName + " does not"
+ " exist in table " + table);
return coldef;
}
/*
* Updates the current row in the table.
*
* @param c Ordered Vector of column names
* @param v Ordered Vector (must match order of c) of values
* @see tinySQLTable#UpdateCurrentRow
*/
public void UpdateCurrentRow(Vector c, Vector v) throws tinySQLException
{
/*
* The Vectors v and c are expected to have the
* same number of elements. It is also expected
* that the elements correspond to each other,
* such that value 1 of Vector v corresponds to
* column 1 of Vector c, and so forth.
*/
for (int i = 0; i < v.size(); i++)
{
/*
* Get the column name and the value, and
* invoke UpdateCol() to update it.
*/
String column = ((String)c.elementAt(i)).toUpperCase();
String value = (String)v.elementAt(i);
UpdateCol(column, value);
}
}
/*
* Position the record pointer at the top of the table.
*
* @see tinySQLTable#GoTop
*/
public void GoTop() throws tinySQLException
{
currentRowCache = null;
currentRecordNumber = 0;
eof = false;
}
/*
* Advance the record pointer to the next record.
*
* @see tinySQLTable#NextRecord
*/
public boolean NextRecord() throws tinySQLException
{
currentRowCache = null;
if (currentRecordNumber < dbfHeader.numRecords)
{
currentRecordNumber++;
eof = false;
return true;
} else {
eof = true;
return false;
}
}
/*
* Insert a row. If c or v == null, insert a blank row
*
* @param c Ordered Vector of column names
* @param v Ordered Vector (must match order of c) of values
* @see tinySQLTable#InsertRow()
*
*/
public void InsertRow(Vector c, Vector v) throws tinySQLException
{
try
{
/*
* Go to the end of the file, then write out the not deleted indicator
*/
ftbl.seek( ftbl.length() );
ftbl.write(RECORD_IS_NOT_DELETED);
/*
* Write out a blank record
*/
for (int i = 1; i < dbfHeader.recordLength; i++)
{
ftbl.write(' ');
}
int numRec = (int)dbfHeader.numRecords + 1;
currentRecordNumber = numRec;
dbfHeader.setNumRecords(ftbl, numRec);
} catch (Exception e) {
if ( tinySQLGlobals.DEBUG ) e.printStackTrace();
throw new tinySQLException(e.getMessage());
}
if (c != null && v != null)
UpdateCurrentRow(c, v);
else
dbfHeader.setTimestamp(ftbl);
}
/*
* Retrieve a column's string value from the current row.
*
* @param column the column name
* @see tinySQLTable#GetCol
*/
public String GetCol(String colName) throws tinySQLException
{
int foundDot;
String columnName;
columnName = colName;
foundDot = columnName.indexOf(".");
if ( foundDot > -1 )
columnName = columnName.substring(foundDot + 1);
tsColumn coldef = (tsColumn) column_info.get(columnName);
if (currentRowCache == null)
currentRowCache = _GetCol(ftbl, dbfHeader, currentRecordNumber);
return getColumn (coldef, currentRowCache);
}
/*
* Extracts a column from the given row. The row is given as a string.
* If coldef is null, the special delete-flag is returned (Position 0 of a row).
*
* @param coldef the column definition, which tells what content to extract from the row
* @param row the row as an string contains all column data
* @returns a substring of row.
*/
public static String getColumn (tsColumn coldef, String row)
{
if ( row == (String)null ) System.out.println("Row is null");
else if ( row.length() == 0 ) System.out.println("Row has 0 length");
if (coldef == null)
return row.substring (0,1);
return row.substring(coldef.position, coldef.position + coldef.size);
}
/*
* Retrieve a column's string value from the given row and given colName
* @param ff the file handle
* @param colName the column name
* @param the wanted record (starts with 1)
* @see tinySQLTable#GetCol
*
* @author Thomas Morgner <mgs@sherito.org> This function retrieves a
* row, perhaps the name should changed to reflect the new function.
*/
public static String _GetCol(RandomAccessFile ff, DBFHeader dbfHeader,
int currentRow) throws tinySQLException
{
try
{
/*
* Seek the starting offset of the current record,
* as indicated by currentRow
*/
ff.seek(dbfHeader.headerLength + (currentRow - 1) * dbfHeader.recordLength);
/*
* Fully read a byte array out to the length of the record and convert
* it into a String.
*/
byte[] b = new byte[dbfHeader.recordLength];
ff.readFully(b);
return new String(b, Utils.encode); // "Cp437"
} catch (Exception e) {
throw new tinySQLException(e.getMessage());
}
}
/*
* Update a single column.
*
* @param column the column name
* @param value the String value with which update the column
* @see tinySQLTable#UpdateCol
*
*/
public void UpdateCol( String colName, String value ) throws tinySQLException
{
String shortColumnName;
try
{
/*
* If it's the pseudo column _DELETED, return
*/
if (colName.equals("_DELETED")) return;
/*
* Retrieve the tsColumn object which corresponds to this column.
*/
shortColumnName = tinySQLGlobals.getShortName(colName);
tsColumn column = (tsColumn) column_info.get(shortColumnName);
if (column == null)
throw new tinySQLException("Can't update field=" + colName);
if ( Utils.isDateColumn(column.type) )
{
/*
* Convert non-blank dates to the standard YYYYMMDD format.
*/
if ( value.trim().length() > 0 )
value = UtilString.dateValue(value);
}
/*
* Seek the starting offset of the current record,
* as indicated by currentRecordNumber
*/
ftbl.seek(dbfHeader.headerLength + (currentRecordNumber - 1) * dbfHeader.recordLength + column.position);
/*
* Enforce the correct column length, transform to byte and write to file
*/
value = Utils.forceToSize(value, column.size, " ");
byte[] b = value.getBytes(Utils.encode);
ftbl.write(b);
dbfHeader.setTimestamp(ftbl);
} catch (Exception e) {
throw new tinySQLException(e.getMessage());
}
}
/*
* Delete the current row.
*
* @see tinySQLTable#DeleteRow
*
*/
public void DeleteRow() throws tinySQLException
{
try
{
ftbl.seek(dbfHeader.headerLength + (currentRecordNumber - 1) * dbfHeader.recordLength);
ftbl.write(RECORD_IS_DELETED);
} catch (Exception e) {
throw new tinySQLException(e.getMessage());
}
}
/*
* Is the current row deleted?
*
* @see tinySQLTable#isDeleted()
*/
public boolean isDeleted() throws tinySQLException
{
return ((GetCol("_DELETED")).charAt(0) == RECORD_IS_DELETED); // "*";
}
/*
* Checks whether the row is deleted.
*/
public static boolean isDeleted(RandomAccessFile ff, DBFHeader dbfHeader, int currentRow) throws tinySQLException
{
char del = _GetCol(ff, dbfHeader, currentRow).charAt(0); // "_DELETED"
return del == RECORD_IS_DELETED;
}
/*
* Check if record is marked as deleted
* @param record the record string (the first byte '*' marks a deleted record)
*/
public static boolean isDeleted(String record)
{
if (record.charAt(IS_DELETED_INDEX) == RECORD_IS_DELETED)
return true; // '*'
return false; // ' '
}
/***************************************************************************
*
* End methods implemented from tinySQLTable.java
* the rest of this stuff is private methods
* for dbfFileTable.
*
* @return Length in bytes of one row or 0 if not known
*/
public int getRecordLength()
{
return dbfHeader.recordLength;
}
public String toString()
{
StringBuffer outputBuffer;
outputBuffer = new StringBuffer();
outputBuffer.append("Table " + table + ", path " + fullPath
+ ", file " + ftbl.toString());
return outputBuffer.toString();
}
/*
* opens a DBF file. This is based on Pratap Pereira's
* Xbase.pm perl module
* @return column definition list (HashTable)
*
* @author Thomas Morgner <mgs@sherito.org> added check for
* file exists, before the file is opened. Opening a non existing
* file will create a new file, and we get errors while trying
* to read the non-existend headers
*/
Hashtable open_dbf() throws tinySQLException
{
try
{
File f = new File (fullPath);
if ( tinySQLGlobals.DEBUG )
System.out.println("Try to open " + f.getAbsolutePath());
if (!f.exists() )
{
throw new tinySQLException ("Unable to open " + f.getAbsolutePath()
+ " - does not exist. or can't be read.");
} else if (!f.canRead () ) {
throw new tinySQLException ("Unable to open " + f.getAbsolutePath()
+ " - file can't be read (permissions?).");
}
if (f.canWrite ())
{
ftbl = new RandomAccessFile(f, "rw");
} else {
/*
* Open readonly if the file is not writeable. Needed for
* databases on CD-Rom
*/
ftbl = new RandomAccessFile(f, "r");
}
/*
* Read the first 32 bytes ...
*/
dbfHeader = new DBFHeader(ftbl);
/*
* read the column info (each is a 32 byte bulk) ...
*/
Hashtable coldef_list = new Hashtable();
columnNameKeys = new Vector();
int locn = 0; // offset of the current column
for (int i = 1; i <= dbfHeader.numFields; i++)
{
tsColumn coldef = dbfFile.readColdef(ftbl, table, i, locn);
locn += coldef.size; // increment locn by the length of this field.
coldef_list.put(coldef.name, coldef);
columnNameKeys.addElement(coldef.name);
}
fileOpen = true;
return coldef_list;
} catch (Exception e) {
if ( tinySQLGlobals.DEBUG ) e.printStackTrace();
throw new tinySQLException(e.getMessage());
}
}
}
|