/*
DBFReader
Class for reading the records assuming that the given
InputStream comtains DBF data.
This file is part of JavaDBF packege.
Author: anil@linuxense.com
License: LGPL (http://www.gnu.org/copyleft/lesser.html)
$Id: DBFReader.java,v 1.8 2004/03/31 10:54:03 anil Exp $
Modified by Dr. John Lindsay March 19, 2012.
*/
package whitebox.geospatialfiles.shapefile.attributes;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.GregorianCalendar;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.BOOLEAN;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.DATE;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.FLOAT;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.MEMO;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.NUMERIC;
import static whitebox.geospatialfiles.shapefile.attributes.DBFField.DBFDataType.STRING;
/**
* DBFReader class can creates objects to represent DBF data.
*
* This Class is used to read data from a DBF file. Meta data and records can be
* queried against this document.
*
* <p> DBFReader cannot write anything to a DBF file. For creating DBF files use
* DBFWriter.
*
* <p> Fetching record is possible only in the forward direction and cannot
* re-wound. In such situation, a suggested approach is to reconstruct the
* object.
*
* <p> The nextRecord() method returns an array of Objects and the types of
* these Object are as follows:
*
* <table> <tr> <th>xBase Type</th><th>Java Type</th> </tr>
*
* <tr> <td>C</td><td>String</td> </tr> <tr> <td>N</td><td>Integer</td> </tr>
* <tr> <td>F</td><td>Double</td> </tr> <tr> <td>L</td><td>Boolean</td> </tr>
* <tr> <td>D</td><td>java.util.Date</td> </tr> </table>
*
*/
public class DBFReader extends DBFBase {
//private DataInputStream dataInputStream;
private DBFHeader header;
private String fileName;
private int currentRecord = -1;
/*
* Class specific variables
*/
private boolean isClosed = true;
public DBFReader(String str) throws DBFException {
try {
DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(str)));
DataInputStream dataInputStream = new DataInputStream(in);
this.isClosed = false;
this.header = new DBFHeader();
this.header.read(dataInputStream);
this.fileName = str;
// /* it might be required to leap to the start of records at times */
// int t_dataStartIndex = this.header.headerLength - (32 + (32 * this.header.fieldArray.length)) - 1;
// if (t_dataStartIndex > 0) {
//
// dataInputStream.skip(t_dataStartIndex);
// }
} catch (IOException e) {
throw new DBFException(e.getMessage());
}
}
/**
* Initializes a DBFReader object.
*
* When this constructor returns the object will have completed reading the
* hader (meta date) and header information can be quried there on. And it
* will be ready to return the first row.
*
* @param InputStream where the data is read from.
*/
public DBFReader(InputStream in) throws DBFException {
try {
DataInputStream dataInputStream = new DataInputStream(in);
this.isClosed = false;
this.header = new DBFHeader();
this.header.read(dataInputStream);
// /* it might be required to leap to the start of records at times */
// int t_dataStartIndex = this.header.headerLength - (32 + (32 * this.header.fieldArray.length)) - 1;
// if (t_dataStartIndex > 0) {
//
// dataInputStream.skip(t_dataStartIndex);
// }
} catch (IOException e) {
throw new DBFException(e.getMessage());
}
}
public int getCurrentRecord() {
return currentRecord;
}
public void setCurrentRecord(int currentRecord) {
this.currentRecord = currentRecord;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.header.year + "/" + this.header.month + "/" + this.header.day + "\n"
+ "Total records: " + this.header.numberOfRecords
+ "\nHEader length: " + this.header.headerLength
+ "");
for (int i = 0; i < this.header.fieldArray.length; i++) {
sb.append(this.header.fieldArray[i].getName());
sb.append("\n");
}
return sb.toString();
}
/**
* Returns the number of records in the DBF.
*/
public int getRecordCount() {
return this.header.numberOfRecords;
}
/**
* Returns the asked Field. In case of an invalid index, it returns a
* ArrayIndexOutofboundsException.
*
* @param index. Index of the field. Index of the first field is zero.
*/
public DBFField getField(int index)
throws DBFException {
if (isClosed) {
throw new DBFException("Source is not open");
}
return this.header.fieldArray[index];
}
/**
* Retrieves all fields in this database.
*
* @return DBFField array
* @throws DBFException
*/
public DBFField[] getAllFields() throws DBFException {
if (isClosed) {
throw new DBFException("Source is not open");
}
return this.header.fieldArray;
}
/**
* Returns the number of field in the DBF.
*/
public int getFieldCount()
throws DBFException {
if (isClosed) {
throw new DBFException("Source is not open");
}
if (this.header.fieldArray != null) {
return this.header.fieldArray.length;
}
return -1;
}
/**
* Returns a String array of fields.
*
* @return String array
*/
public String[] getAttributeTableFieldNames() {
try {
int numberOfFields = this.getFieldCount();
String[] ret = new String[numberOfFields];
for (int i = 0; i < numberOfFields; i++) {
DBFField field = this.getField(i);
ret[i] = field.getName();
}
return ret;
} catch (DBFException dbfe) {
System.out.println(dbfe);
return null;
}
}
/**
* Reads the returns the next row in the DBF stream. @returns The next row
* as an Object array. Types of the elements these arrays follow the
* convention mentioned in the class description.
*/
public Object[] nextRecord() throws DBFException {
if (isClosed) {
throw new DBFException("Source is not open");
}
currentRecord++;
if (currentRecord < 0) {
throw new DBFException("Record number is out of bounds.");
}
if (currentRecord >= this.header.numberOfRecords) {
return null;
}
return getRecord(currentRecord);
// Object recordObjects[] = new Object[this.header.fieldArray.length];
// try {
//
// boolean isDeleted = false;
// do {
//
// if (isDeleted) {
//
// dataInputStream.skip(this.header.recordLength - 1);
// }
//
// int t_byte = dataInputStream.readByte();
// if (t_byte == END_OF_DATA) {
//
// return null;
// }
//
// isDeleted = (t_byte == '*');
// } while (isDeleted);
//
// for (int i = 0; i < this.header.fieldArray.length; i++) {
//
// switch (this.header.fieldArray[i].getDataType()) {
//
// case 'C':
//
// byte b_array[] = new byte[this.header.fieldArray[i].getFieldLength()];
// dataInputStream.read(b_array);
// recordObjects[i] = new String(b_array, characterSetName);
// break;
//
// case 'D':
//
// byte t_byte_year[] = new byte[4];
// dataInputStream.read(t_byte_year);
//
// byte t_byte_month[] = new byte[2];
// dataInputStream.read(t_byte_month);
//
// byte t_byte_day[] = new byte[2];
// dataInputStream.read(t_byte_day);
//
// try {
//
// GregorianCalendar calendar = new GregorianCalendar(
// Integer.parseInt(new String(t_byte_year)),
// Integer.parseInt(new String(t_byte_month)) - 1,
// Integer.parseInt(new String(t_byte_day)));
//
// recordObjects[i] = calendar.getTime();
// } catch (NumberFormatException e) {
// /* this field may be empty or may have improper value set */
// recordObjects[i] = null;
// }
//
// break;
//
// case 'F':
//
// try {
//
// byte t_float[] = new byte[this.header.fieldArray[i].getFieldLength()];
// dataInputStream.read(t_float);
// t_float = Utils.trimLeftSpaces(t_float);
// if (t_float.length > 0 && !Utils.contains(t_float, (byte) '?')) {
//
// recordObjects[i] = new Float(new String(t_float));
// } else {
//
// recordObjects[i] = null;
// }
// } catch (NumberFormatException e) {
//
// throw new DBFException("Failed to parse Float: " + e.getMessage());
// }
//
// break;
//
// case 'N':
//
// try {
//
// byte t_numeric[] = new byte[this.header.fieldArray[i].getFieldLength()];
// dataInputStream.read(t_numeric);
// t_numeric = Utils.trimLeftSpaces(t_numeric);
//
// if (t_numeric.length > 0 && !Utils.contains(t_numeric, (byte) '?')) {
//
// recordObjects[i] = new Double(new String(t_numeric));
// } else {
//
// recordObjects[i] = null;
// }
// } catch (NumberFormatException e) {
//
// throw new DBFException("Failed to parse Number: " + e.getMessage());
// }
//
// break;
//
// case 'L':
//
// byte t_logical = dataInputStream.readByte();
// if (t_logical == 'Y' || t_logical == 't' || t_logical == 'T' || t_logical == 't') {
//
// recordObjects[i] = Boolean.TRUE;
// } else {
//
// recordObjects[i] = Boolean.FALSE;
// }
// break;
//
// case 'M':
// // TODO Later
// recordObjects[i] = new String("null");
// break;
//
// default:
// recordObjects[i] = new String("null");
// }
// }
// } catch (EOFException e) {
//
// return null;
// } catch (IOException e) {
//
// throw new DBFException(e.getMessage());
// }
// return recordObjects;
}
/**
* Reads the returns the <i>n</i>th row in the DBF stream.
*
* @param n The zero-based record number.
* @return The <i>n</i>th row as an Object array. Types of the elements
* these arrays follow the convention mentioned in the class description.
* @throws DBFException
*/
public Object[] getRecord(int n) throws DBFException {
currentRecord = n;
if (currentRecord < 0) {
throw new DBFException("Record number is out of bounds.");
}
if (currentRecord >= this.header.numberOfRecords) {
return null;
}
if (isClosed) {
throw new DBFException("Source is not open");
}
Object recordObjects[] = new Object[this.header.fieldArray.length];
RandomAccessFile rIn = null;
ByteBuffer buf;
FileChannel inChannel = null;
try {
buf = ByteBuffer.allocate(this.header.recordLength);
rIn = new RandomAccessFile(this.fileName, "r");
inChannel = rIn.getChannel();
int pos = (32 + (32 * this.header.fieldArray.length)) + 1 + n * this.header.recordLength;
inChannel.position(pos);
inChannel.read(buf);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.rewind();
if (buf.get() == END_OF_DATA) {
return null;
} // record has been deleted
for (int i = 0; i < this.header.fieldArray.length; i++) {
switch (this.header.fieldArray[i].getDataType()) {
case STRING:
byte b_array[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(b_array);
buf.get(b_array);
recordObjects[i] = new String(b_array, characterSetName);
break;
case DATE:
byte t_byte_year[] = new byte[4];
//dataInputStream.read(t_byte_year);
buf.get(t_byte_year);
byte t_byte_month[] = new byte[2];
//dataInputStream.read(t_byte_month);
buf.get(t_byte_month);
byte t_byte_day[] = new byte[2];
//dataInputStream.read(t_byte_day);
buf.get(t_byte_day);
try {
GregorianCalendar calendar = new GregorianCalendar(
Integer.parseInt(new String(t_byte_year)),
Integer.parseInt(new String(t_byte_month)) - 1,
Integer.parseInt(new String(t_byte_day)));
recordObjects[i] = calendar.getTime();
} catch (NumberFormatException e) {
/*
* this field may be empty or may have improper
* value set
*/
recordObjects[i] = null;
}
break;
case FLOAT:
try {
byte t_float[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(t_float);
buf.get(t_float);
t_float = Utils.trimLeftSpaces(t_float);
if (t_float.length > 0 && !Utils.contains(t_float, (byte) '?')) {
recordObjects[i] = new Float(new String(t_float));
} else {
recordObjects[i] = null;
}
} catch (NumberFormatException e) {
throw new DBFException("Failed to parse Float: " + e.getMessage());
}
break;
case NUMERIC:
try {
byte t_numeric[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(t_numeric);
buf.get(t_numeric);
t_numeric = Utils.trimLeftSpaces(t_numeric);
if (t_numeric.length > 0 && !Utils.contains(t_numeric, (byte) '?')) {
recordObjects[i] = new Double(new String(t_numeric));
} else {
recordObjects[i] = null;
}
} catch (NumberFormatException e) {
throw new DBFException("Failed to parse Number: " + e.getMessage());
}
break;
case BOOLEAN:
byte t_logical = buf.get();
if (t_logical == 'Y' || t_logical == 't' || t_logical == 'T' || t_logical == 't') {
recordObjects[i] = Boolean.TRUE;
} else {
recordObjects[i] = Boolean.FALSE;
}
break;
case MEMO:
// TODO Later
recordObjects[i] = new String("null");
break;
default:
recordObjects[i] = new String("null");
}
}
} catch (EOFException e) {
return null;
} catch (IOException e) {
throw new DBFException(e.getMessage());
} finally {
if (rIn != null) {
try {
rIn.close();
} catch (Exception e) {
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (Exception e) {
}
}
}
return recordObjects;
}
/**
* Reads the records from <i>startingRecord</i> to <i>endingRecord</i> from the table.
* @param startingRecord the first record read (zero-based).
* @param endingRecord the last record read (note the range is inclusive of endingRecord).
* @return an Object[] array where each element is another Object[] array of record values.
* @throws DBFException
*/
public Object[] getRecords(int startingRecord, int endingRecord) throws DBFException {
if (startingRecord < 0) {
throw new DBFException("Record number is out of bounds.");
}
if (endingRecord >= this.header.numberOfRecords) {
endingRecord = this.header.numberOfRecords - 1;
}
currentRecord = endingRecord;
if (isClosed) {
throw new DBFException("Source is not open");
}
int numRecsRead = endingRecord - startingRecord + 1;
Object returnRecords[] = new Object[numRecsRead];
RandomAccessFile rIn = null;
ByteBuffer buf;
FileChannel inChannel = null;
try {
int numBytesToRead = this.header.recordLength * numRecsRead;
buf = ByteBuffer.allocate(numBytesToRead);
rIn = new RandomAccessFile(this.fileName, "r");
inChannel = rIn.getChannel();
int pos = (32 + (32 * this.header.fieldArray.length)) + 1 + startingRecord * this.header.recordLength;
inChannel.position(pos);
inChannel.read(buf);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.rewind();
for (int n = startingRecord; n <= endingRecord; n++) {
if (buf.get() == END_OF_DATA) {
return null;
} // record has been deleted
Object recordObjects[] = new Object[this.header.fieldArray.length];
for (int i = 0; i < this.header.fieldArray.length; i++) {
switch (this.header.fieldArray[i].getDataType()) {
case STRING:
byte b_array[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(b_array);
buf.get(b_array);
recordObjects[i] = new String(b_array, characterSetName);
break;
case DATE:
byte t_byte_year[] = new byte[4];
//dataInputStream.read(t_byte_year);
buf.get(t_byte_year);
byte t_byte_month[] = new byte[2];
//dataInputStream.read(t_byte_month);
buf.get(t_byte_month);
byte t_byte_day[] = new byte[2];
//dataInputStream.read(t_byte_day);
buf.get(t_byte_day);
try {
GregorianCalendar calendar = new GregorianCalendar(
Integer.parseInt(new String(t_byte_year)),
Integer.parseInt(new String(t_byte_month)) - 1,
Integer.parseInt(new String(t_byte_day)));
recordObjects[i] = calendar.getTime();
} catch (NumberFormatException e) {
/*
* this field may be empty or may have improper
* value set
*/
recordObjects[i] = null;
}
break;
case FLOAT:
try {
byte t_float[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(t_float);
buf.get(t_float);
t_float = Utils.trimLeftSpaces(t_float);
if (t_float.length > 0 && !Utils.contains(t_float, (byte) '?')) {
recordObjects[i] = new Float(new String(t_float));
} else {
recordObjects[i] = null;
}
} catch (NumberFormatException e) {
throw new DBFException("Failed to parse Float: " + e.getMessage());
}
break;
case NUMERIC:
try {
byte t_numeric[] = new byte[this.header.fieldArray[i].getFieldLength()];
//dataInputStream.read(t_numeric);
buf.get(t_numeric);
t_numeric = Utils.trimLeftSpaces(t_numeric);
if (t_numeric.length > 0 && !Utils.contains(t_numeric, (byte) '?')) {
recordObjects[i] = new Double(new String(t_numeric));
} else {
recordObjects[i] = null;
}
} catch (NumberFormatException e) {
throw new DBFException("Failed to parse Number: " + e.getMessage());
}
break;
case BOOLEAN:
byte t_logical = buf.get();
if (t_logical == 'Y' || t_logical == 't' || t_logical == 'T' || t_logical == 't') {
recordObjects[i] = Boolean.TRUE;
} else {
recordObjects[i] = Boolean.FALSE;
}
break;
case MEMO:
// TODO Later
recordObjects[i] = new String("null");
break;
default:
recordObjects[i] = new String("null");
}
}
returnRecords[n - startingRecord] = recordObjects;
}
} catch (EOFException e) {
return null;
} catch (IOException e) {
throw new DBFException(e.getMessage());
} finally {
if (rIn != null) {
try {
rIn.close();
} catch (Exception e) {
}
}
if (inChannel != null) {
try {
inChannel.close();
} catch (Exception e) {
}
}
}
return returnRecords;
}
}