package org.geotools.dbffile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.URL;
import java.net.URLConnection;
import java.util.Vector;
import jeql.io.EndianDataInputStream;
/**
*
* This class represents a DBF (or DBase) file.<p>
* Construct it with a URL or File (including the .dbf)
* this causes the header and field definitions to be read.<p>
* Later queries return rows or columns of the database.<p>
* If a URL is specified then the whole file is read into memory<br>
* if a file is specified then a randomAccess system is used.<br>
*<hr>
* @author <a href="mailto:ian@geog.leeds.ac.uk">Ian Turton</a> Centre for
* Computaional Geography, University of Leeds, LS2 9JT, 1998.
* <br>
* mod to getStringCol by James Macgill.
*/
public class Dbf implements DbfConsts{
static final boolean DEBUG=false;
static final String DBC="Dbf->";
int dbf_id;
int last_update_d,last_update_m,last_update_y;
int last_rec;
int data_offset;
int rec_size;
StringBuffer records[];
int position=0;
boolean hasmemo;
boolean isFile = false;
RandomAccessFile rFile;
EndianDataInputStream dFile;
int filesize,numfields;
public DbfFieldDef fielddef[];
/**
* Constructor, opens the file and reads the header infomation.
* @param file The file to be opened, includes path and .dbf
* @exception java.io.IOException If the file can't be opened.
* @exception DbfFileException If there is an error reading header.
*/
public Dbf(URL url) throws java.io.IOException,DbfFileException{
if(DEBUG)System.out.println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "+DBC);
URLConnection uc = url.openConnection();
InputStream in = uc.getInputStream();
EndianDataInputStream sfile = new EndianDataInputStream(in);
init(sfile);
}
public Dbf(InputStream in) throws java.io.IOException,DbfFileException{
if(DEBUG)System.out.println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "+DBC);
EndianDataInputStream sfile = new EndianDataInputStream(in);
init(sfile);
}
public Dbf(String name) throws java.io.IOException,DbfFileException{
if(DEBUG)System.out.println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "+DBC);
URL url = new URL(name);
URLConnection uc = url.openConnection();
InputStream in = uc.getInputStream();
EndianDataInputStream sfile = new EndianDataInputStream(in);
init(sfile);
}
public Dbf(File file) throws java.io.IOException,DbfFileException{
if(DEBUG)System.out.println("---->uk.ac.leeds.ccg.dbffile.Dbf constructed. Will identify itself as "+DBC);
InputStream in = new FileInputStream(file);
EndianDataInputStream sfile = new EndianDataInputStream(in);
rFile=new RandomAccessFile(file,"r");
isFile=true;
init(sfile);
}
/**
* Returns the date of the last update of the file as a string.
*/
public String getLastUpdate(){
String date=last_update_d+"/"+(last_update_m+1)+"/"+(1900+last_update_y);
return date;
}
/**
* Returns the number of records in the database file.
*/
public int getLastRec(){
return last_rec;
}
/**
* Returns the size of the records in the database file.
*/
public int getRecSize(){
return rec_size;
}
/**
* Returns the number of fields in the records in the database file.
*/
public int getNumFields(){
return numfields;
}
/**
* looks up the field number for the given named column
* @param name A String for the name to look up
* @return int The col number for the field, -1 if field could not be found
*/
public int getFieldNumber(String name){
for(int i=0;i<numfields;i++){
//System.out.println(i);
if(name.equalsIgnoreCase(fielddef[i].fieldname.toString())){
return i;
}
}
return -1;//not found
}
/**
* Returns the size of the database file.
*/
public int getFileSize(){
return filesize;
}
public StringBuffer getFieldName(int col){
if(col>=numfields) throw new IllegalArgumentException(DBC+"column number specified is invalid. It's higher than the amount of columns available "+numfields);
return fielddef[col].fieldname;
}
public char getFieldType(int col){
if(col>=numfields) throw new IllegalArgumentException(DBC+"column number specified is invalid. It's higher than the amount of columns available"+numfields);
return fielddef[col].fieldtype;
}
/**
* initailizer, allows the use of multiple constructers in later
* versions.
*/
private void init(EndianDataInputStream sfile)throws
IOException,DbfFileException {
DbfFileHeader head = new DbfFileHeader(sfile);
int widthsofar;
dFile=sfile;
fielddef = new DbfFieldDef[numfields];
widthsofar=1;
for(int index=0;index<numfields;index++){
fielddef[index] = new DbfFieldDef();
fielddef[index].setup(widthsofar,sfile);
widthsofar+=fielddef[index].fieldlen;
}
sfile.skipBytes(1); // end of field defs marker
if(!isFile){
records=GrabFile();
}
}
/**
* Internal Class to hold information from the header of the file
*/
class DbfFileHeader{
/**
* Reads the header of a dbf file.
* @param LEDataInputStream file Stream attached to the input file
* @exception IOException read error.
*/
public DbfFileHeader(EndianDataInputStream file) throws IOException {
getDbfFileHeader(file);
}
private void getDbfFileHeader(EndianDataInputStream file) throws IOException {
int len;
dbf_id=(int)file.readUnsignedByteLE();
if(DEBUG)System.out.println(DBC+"Header id "+dbf_id);
if(dbf_id==3) hasmemo=true;
else hasmemo=false;
last_update_y=(int)file.readUnsignedByteLE();
last_update_m=(int)file.readUnsignedByteLE();
last_update_d=(int)file.readUnsignedByteLE();
if(DEBUG)System.out.print(DBC+"last update ");
if(DEBUG)System.out.print(last_update_d);
if(DEBUG)System.out.print("/");
if(DEBUG)System.out.print(last_update_m);
if(DEBUG)System.out.print("/");
if(DEBUG)System.out.println(last_update_y);
last_rec=file.readIntLE();
if(DEBUG)System.out.print(DBC+"last rec ");
if(DEBUG)System.out.println(last_rec);
data_offset=file.readShortLE();
//data_offset=0;
//System.out.println("x = "+file.readUnsignedByte()+" " +
//file.readUnsignedByte());
if(DEBUG)System.out.print(DBC+"data offset ");
if(DEBUG)System.out.println(data_offset);
rec_size=file.readShortLE();
if(DEBUG)System.out.print(DBC+"rec_size ");
if(DEBUG)System.out.println(rec_size);
filesize=(rec_size * last_rec) + data_offset+1;
numfields = (data_offset - DBF_BUFFSIZE -1)/DBF_BUFFSIZE;
if(DEBUG)System.out.print(DBC+"num fields ");
if(DEBUG)System.out.println(numfields);
if(DEBUG)System.out.print(DBC+"file size ");
if(DEBUG)System.out.println(filesize);
file.skipBytes(20);
}
}
/**
* gets the next record and returns it as a string. This method works on
* a sequential stream and can not go backwards. Only useful if you want
* to read the whole file in one.
* @exception java.io.IOException on read error.
*/
public StringBuffer GetNextDbfRec()throws java.io.IOException{
return records[position++];
}
private StringBuffer GrabNextDbfRec()throws java.io.IOException{
StringBuffer record = new StringBuffer(rec_size+numfields);
//Modifed to use Hisaji ONO's approach for reading multi byte character sets
byte[] strbuf = new byte[rec_size]; // <---- byte array buffer fo storing string's byte data
for(int i=0;i<rec_size;i++){
strbuf[i] = dFile.readByteLE(); // <---- read string's byte data
}
record.append(new String(strbuf)); // <- append byte array to String Buffer
return record;
}
private StringBuffer[] GrabFile() throws java.io.IOException{
StringBuffer records[] = new StringBuffer[last_rec];
for(int i=0;i<last_rec;i++)
records[i]=GrabNextDbfRec();
return records;
}
/**
* fetches the <i>row</i>th row of the file
* @param row - the row to fetch
* @exception java.io.IOException on read error.
*/
public StringBuffer GetDbfRec(int row)throws java.io.IOException{
StringBuffer record;// = new StringBuffer(rec_size);
if(!isFile){
return record = new StringBuffer(records[row].toString());
}
else{
record = new StringBuffer(rec_size+numfields);
rFile.seek(data_offset+(rec_size*row));
//Modifed to use Hisaji ONO's approach for reading multi byte character sets
byte[] strbuf = new byte[rec_size]; // <---- byte array buffer fo storing string's byte data
for(int i=0;i<rec_size;i++){
strbuf[i] = dFile.readByteLE(); // <---- read string's byte data
}
record.append(new String(strbuf)); // <- append byte array to String Buffer
return record;
}
}
/**
* fetches the <i>row</i>th row of the file and parses it into an vector
* of objects.
* @param row - the row to fetch
* @exception java.io.IOException on read error.
*/
public Vector ParseDbfRecord(int row)throws java.io.IOException{
return ParseRecord(GetDbfRec(row));
}
/**
* Parses the record stored in the StringBuffer rec into a vector of
* objects
* @param StringBuffer rec - the record to be parsed.
*/
public Vector ParseRecord(StringBuffer rec){
Vector record=new Vector(numfields);
String t;
Integer I=new Integer(0);
Float F=new Float(0.0);
t = rec.toString();
for(int i=0;i<numfields;i++){
if(DEBUG)System.out.println(DBC+"type "+fielddef[i].fieldtype);
if(DEBUG)System.out.println(DBC+"start "+fielddef[i].fieldstart);
if(DEBUG)System.out.println(DBC+"len "+fielddef[i].fieldlen);
if(DEBUG)System.out.println(DBC+""+t.substring(fielddef[i].fieldstart,
fielddef[i].fieldstart+fielddef[i].fieldlen));
switch(fielddef[i].fieldtype){
case 'C':
record.addElement(t.substring(fielddef[i].fieldstart,
fielddef[i].fieldstart+fielddef[i].fieldlen));
break;
case 'N':
case 'F':
if(fielddef[i].fieldnumdec==0)
try{
record.addElement(I.decode(t.substring(
fielddef[i].fieldstart,fielddef[i].fieldstart+fielddef[i].fieldlen)));
}catch(java.lang.NumberFormatException e){
record.addElement(new Integer(0));
}
else
try{
record.addElement(F.valueOf(t.substring(
fielddef[i].fieldstart,fielddef[i].fieldstart+fielddef[i].fieldlen)));
}catch(java.lang.NumberFormatException e){
record.addElement(new Float(0.0));
}
break;
default:
if(DEBUG)System.out.println(DBC+"Oh - don't know how to parse "
+fielddef[i].fieldtype);
}
}
return record;
}
/**
* Fetches a column of Integers from the database file.
* @param int col - the column to fetch
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public Integer[] getIntegerCol(int col )
throws java.io.IOException,DbfFileException {
return getIntegerCol(col,0,last_rec);
}
/**
* Fetches a part column of Integers from the database file.
* @param int col - the column to fetch
* @param int start - the row to start fetching from
* @param int end - the row to stop fetching at.
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public Integer[] getIntegerCol(int col, int start, int end)
throws java.io.IOException,DbfFileException {
Integer column[]=new Integer[end-start];
String record = new String();
StringBuffer sb = new StringBuffer(numfields);
int k=0,i=0;
if(col>=numfields)
throw new DbfFileException(DBC+"No Such Column in file: "+col);
if(fielddef[col].fieldtype!='N')
throw new DbfFileException(DBC+"Column "+col+" is not Integer "+
fielddef[col].fieldtype);
if(start<0)
throw new DbfFileException(DBC+"Start must be >= 0");
if(end>last_rec)
throw new DbfFileException(DBC+"End must be <= "+last_rec);
// move to start of data
try{
for(i=start;i<end;i++){
sb.setLength(0);
sb=GetDbfRec(i);
record=sb.toString();
if(DEBUG)System.out.println(DBC+record.substring(fielddef[col].fieldstart,
fielddef[col].fieldstart+fielddef[col].fieldlen).trim()+"*");
column[i-start]=new Integer(record.substring(fielddef[col].fieldstart,
fielddef[col].fieldstart+fielddef[col].fieldlen).trim());
}
}
catch(java.lang.NumberFormatException nfe){
//throw new DbfFileException(DBC+"Column "+col+" contains an non integer id number "+nfe);
// be nicer
column[i-start]=new Integer(0);
}
catch(java.io.EOFException e){
System.err.println(e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos "
);}
catch(java.io.IOException e){
System.err.println(e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos "
);}
return column;
}
/**
* Fetches a column of Floats from the database file.
* @param int col - the column to fetch
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public Float[] getFloatCol(int col) throws DbfFileException,
java.io.IOException{
return getFloatCol(col,0,last_rec);
}
/**
* Fetches a part column of Floats from the database file.
* @param int col - the column to fetch
* @param int start - the row to start fetching from
* @param int end - the row to stop fetching at.
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public Float[] getFloatCol(int col,int start,int end)
throws DbfFileException, java.io.IOException{
Float column[]=new Float[end-start];
String record,st;
StringBuffer sb = new StringBuffer(rec_size);
int k=0,i=0;
if(col>=numfields)
throw new DbfFileException("Dbf->No Such Column in file: "+col);
if(fielddef[col].fieldtype!='F'&&fielddef[col].fieldtype!='N')
throw new DbfFileException("Dbf->Column "+col+" is not Float "
+fielddef[col].fieldtype);
if(start<0)
throw new DbfFileException("Dbf->Start must be >= 0");
if(end>last_rec)
throw new DbfFileException("Dbf->End must be <= "+last_rec);
// move to start of data
try{
for(i=start;i<end;i++){
sb.setLength(0);
sb=GetDbfRec(i);
record=sb.toString();
st=new String(record.substring(fielddef[col].fieldstart,
fielddef[col].fieldstart+fielddef[col].fieldlen)).trim();
if(st.indexOf('.')==-1){
st=st+".0";
}
try{
column[i-start]=new Float(st);
}catch(java.lang.NumberFormatException e){
column[i-start]=new Float(0.0);
}
}
}
catch(java.io.EOFException e){
System.err.println("Dbf->"+e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos ");}
catch(java.io.IOException e){
System.err.println("Dbf->"+e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos ");}
return column;
}
/**
* Fetches a column of Strings from the database file.
* @param int col - the column to fetch
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public String[] getStringCol(int col) throws DbfFileException,
java.io.IOException{
return getStringCol(col,0,last_rec);
}
/**
* Fetches a part column of Strings from the database file.
* @param int col - the column to fetch
* @param int start - the row to start fetching from
* @param int end - the row to stop fetching at.
* @exception java.io.IOException - on read error
* @exception DbfFileException - column is not an Integer.
*/
public String[] getStringCol(int col,int start,int end)
throws DbfFileException, java.io.IOException{
String column[]=new String[end-start];
String record = new String();
StringBuffer sb = new StringBuffer(numfields);
int k=0,i=0;
if(col>=numfields)
throw new DbfFileException("Dbf->No Such Column in file: "+col);
//if(fielddef[col].fieldtype!='C')
//throw new DbfFileException("Column "+col+" is not a String");
if(start<0)
throw new DbfFileException("Dbf->Start must be >= 0");
if(end>last_rec)
throw new DbfFileException("Dbf->End must be <= "+last_rec);
// move to start of data
try{
for(i=start;i<end;i++){
sb.setLength(0);
sb=GetDbfRec(i);
record=sb.toString();
//column[i-start]=new String(record.substring(fielddef[col].fieldstart,
//fielddef[col].fieldstart+fielddef[col].fieldlen));
// replaced to fix bug #547080
column[i-start]=new String(record.getBytes(), fielddef[col].fieldstart,fielddef
[col].fieldlen).trim();
}
}
catch(java.io.EOFException e){
System.err.println("Dbf->"+e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos ");}
catch(java.io.IOException e){
System.err.println("Dbf->"+e);
System.err.println("Dbf->record "+i+" byte "+k+" file pos ");}
return column;
}
}