/*
DBFWriter
Class for defining a DBF structure and addin data to that structure and
finally writing it to an OutputStream.
This file is part of JavaDBF packege.
author: anil@linuxense.com
license: LGPL (http://www.gnu.org/copyleft/lesser.html)
$Id: DBFWriter.java,v 1.9 2004/03/31 10:57:16 anil Exp $
*/
package whitebox.geospatialfiles.shapefile.attributes;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Vector;
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;
/**
* An object of this class can create a DBF file.
*
* Create an object, <br> then define fields by creating DBFField objects
* and<br> add them to the DBFWriter object<br> add records using the
* addRecord() method and then<br> call write() method.
*/
public class DBFWriter extends DBFBase {
/*
* other class variables
*/
private DBFHeader header;
private Vector v_records = new Vector();
private int recordCount = 0;
private RandomAccessFile raf = null;
/*
* Open and append records to an existing DBF
*/
private String fileName;
private boolean appendMode = false;
/**
* Creates an empty Object.
*/
public DBFWriter() {
this.header = new DBFHeader();
}
/**
* Creates a DBFWriter which can append to records to an existing DBF file.
*
* @param dbfFile. The file passed in should be a valid DBF file.
* @exception Throws DBFException if the passed in file does exist but not a
* valid DBF file, or if an IO error occurs.
*/
public DBFWriter(File dbfFile) throws DBFException {
try {
this.fileName = dbfFile.getAbsolutePath();
this.raf = new RandomAccessFile(dbfFile, "rw");
/*
* before proceeding check whether the passed in File object is an
* empty/non-existent file or not.
*/
if (!dbfFile.exists() || dbfFile.length() == 0) {
this.header = new DBFHeader();
return;
}
header = new DBFHeader();
this.header.read(raf);
/*
* position file pointer at the end of the raf
*/
this.raf.seek(this.raf.length() - 1 /*
* to ignore the END_OF_DATA byte at EoF
*/);
} catch (FileNotFoundException e) {
throw new DBFException("Specified file is not found. " + e.getMessage());
} catch (IOException e) {
throw new DBFException(e.getMessage() + " while reading header");
}
this.recordCount = this.header.numberOfRecords;
}
/**
* Creates a DBFWriter which can append to records to an existing DBF file.
*
* @param dbfFileString. The file string passed in should be a valid DBF
* file.
* @exception Throws DBFException if the passed in file does exist but not a
* valid DBF file, or if an IO error occurs.
*/
public DBFWriter(String dbfFileString) throws DBFException {
try {
this.fileName = dbfFileString;
File dbfFile = new File(dbfFileString);
this.raf = new RandomAccessFile(dbfFile, "rw");
/*
* before proceeding check whether the passed in File object is an
* empty/non-existent file or not.
*/
if (!dbfFile.exists() || dbfFile.length() == 0) {
this.header = new DBFHeader();
return;
}
header = new DBFHeader();
this.header.read(raf);
/*
* position file pointer at the end of the raf
*/
this.raf.seek(this.raf.length() - 1 /*
* to ignore the END_OF_DATA byte at EoF
*/);
} catch (FileNotFoundException e) {
throw new DBFException("Specified file is not found. " + e.getMessage());
} catch (IOException e) {
throw new DBFException(e.getMessage() + " while reading header");
}
this.recordCount = this.header.numberOfRecords;
}
/**
* Sets fields.
*/
public void setFields(DBFField[] fields)
throws DBFException {
if (this.header.fieldArray != null) {
throw new DBFException("Fields has already been set");
}
if (fields == null || fields.length == 0) {
throw new DBFException("Should have at least one field");
}
for (int i = 0; i < fields.length; i++) {
if (fields[i] == null) {
throw new DBFException("Field " + (i + 1) + " is null");
}
}
this.header.fieldArray = fields;
try {
if (this.raf != null && this.raf.length() == 0) {
/*
* this is a new/non-existent file. So write header before
* proceeding
*/
this.header.write(this.raf);
}
} catch (IOException e) {
throw new DBFException("Error accesing file");
}
}
/**
* Add a record.
*/
public void addRecord(Object[] values)
throws DBFException {
if (this.header.fieldArray == null) {
throw new DBFException("Fields should be set before adding records");
}
if (values == null) {
throw new DBFException("Null cannot be added as row");
}
if (values.length != this.header.fieldArray.length) {
throw new DBFException("Invalid record. Invalid number of fields in row");
}
for (int i = 0; i < this.header.fieldArray.length; i++) {
if (values[i] == null) {
continue;
}
Class equivalentClass = this.header.fieldArray[i].getDataType().getEquivalentClass();
if (!(values[i].getClass().isAssignableFrom(equivalentClass))) {
throw new DBFException("Invalid value for field " + i);
}
}
if (this.raf == null) {
v_records.addElement(values);
} else {
try {
writeRecord(this.raf, values);
this.recordCount++;
} catch (IOException e) {
throw new DBFException("Error occured while writing record. " + e.getMessage());
}
}
}
/**
* Writes the set data to the OutputStream.
*/
public void write(OutputStream out) throws DBFException {
try {
if (this.raf == null) {
DataOutputStream outStream = new DataOutputStream(out);
this.header.numberOfRecords = v_records.size();
this.header.write(outStream);
/*
* Now write all the records
*/
int t_recCount = v_records.size();
for (int i = 0; i < t_recCount; i++) { /*
* iterate through records
*/
Object[] t_values = (Object[]) v_records.elementAt(i);
writeRecord(outStream, t_values);
}
outStream.write(END_OF_DATA);
outStream.flush();
} else {
/*
* everything is written already. just update the header for
* record count and the END_OF_DATA mark
*/
this.header.numberOfRecords = this.recordCount;
this.raf.seek(0);
this.header.write(this.raf);
this.raf.seek(raf.length());
this.raf.writeByte(END_OF_DATA);
this.raf.close();
}
} catch (IOException e) {
throw new DBFException(e.getMessage());
}
}
public void write()
throws DBFException {
this.write(null);
}
public void updateRecord(int recordNumber, Object[] objectArray) throws IOException {
if (recordNumber < 0) {
throw new DBFException("Record number is out of bounds.");
}
if (recordNumber > this.header.numberOfRecords) {
throw new DBFException("Record number is out of bounds.");
}
if (recordNumber == this.header.numberOfRecords) { // append it to the end of the file
addRecord(objectArray);
}
//RandomAccessFile rOut = null;
ByteBuffer buf;
FileChannel outChannel = null;
try {
buf = ByteBuffer.allocate(this.header.recordLength);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.rewind();
buf.put((byte) ' ');
for (int j = 0; j < this.header.fieldArray.length; j++) { /*
* iterate throught fields
*/
switch (this.header.fieldArray[j].getDataType()) {
case STRING:
if (objectArray[j] != null) {
String str_value = objectArray[j].toString();
buf.put(Utils.textPadding(str_value, characterSetName, this.header.fieldArray[j].getFieldLength()));
} else {
buf.put(Utils.textPadding("", this.characterSetName, this.header.fieldArray[j].getFieldLength()));
}
break;
case DATE:
if (objectArray[j] != null) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime((Date) objectArray[j]);
StringBuffer t_sb = new StringBuffer();
buf.put(String.valueOf(calendar.get(Calendar.YEAR)).getBytes());
buf.put(Utils.textPadding(String.valueOf(calendar.get(Calendar.MONTH) + 1), this.characterSetName, 2, Utils.ALIGN_RIGHT, (byte) '0'));
buf.put(Utils.textPadding(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)), this.characterSetName, 2, Utils.ALIGN_RIGHT, (byte) '0'));
} else {
buf.put(" ".getBytes());
}
break;
case FLOAT:
if (objectArray[j] != null) {
buf.put(Utils.doubleFormating((Double) objectArray[j], this.characterSetName, this.header.fieldArray[j].getFieldLength(), this.header.fieldArray[j].getDecimalCount()));
} else {
buf.put(Utils.textPadding("?", this.characterSetName, this.header.fieldArray[j].getFieldLength(), Utils.ALIGN_RIGHT));
}
break;
case NUMERIC:
if (objectArray[j] != null) {
buf.put(
Utils.doubleFormating((Double) objectArray[j], this.characterSetName, this.header.fieldArray[j].getFieldLength(), this.header.fieldArray[j].getDecimalCount()));
} else {
buf.put(
Utils.textPadding("?", this.characterSetName, this.header.fieldArray[j].getFieldLength(), Utils.ALIGN_RIGHT));
}
break;
case BOOLEAN:
if (objectArray[j] != null) {
if ((Boolean) objectArray[j] == Boolean.TRUE) {
buf.put((byte) 'T');
} else {
buf.put((byte) 'F');
}
} else {
buf.put((byte) '?');
}
break;
case MEMO:
break;
default:
throw new DBFException("Unknown field type " + this.header.fieldArray[j].getDataType());
}
}
//rOut = new RandomAccessFile(this.fileName, "rw");
outChannel = raf.getChannel();
outChannel.lock();
int pos = (32 + (32 * this.header.fieldArray.length)) + 1 + recordNumber * this.header.recordLength;
outChannel.position(pos);
outChannel.write(buf);
} catch (IOException e) {
throw new DBFException(e.getMessage());
} finally {
if (outChannel != null) {
try {
outChannel.force(false);
outChannel.close();
} catch (Exception e) {
}
}
if (raf != null) {
try {
raf.close();
} catch (Exception e) {
}
}
}
}
private void writeRecord(DataOutput dataOutput, Object[] objectArray)
throws IOException {
dataOutput.write((byte) ' ');
for (int j = 0; j < this.header.fieldArray.length; j++) { /*
* iterate throught fields
*/
switch (this.header.fieldArray[j].getDataType()) {
case STRING:
if (objectArray[j] != null) {
String str_value = objectArray[j].toString();
dataOutput.write(Utils.textPadding(str_value, characterSetName, this.header.fieldArray[j].getFieldLength()));
} else {
dataOutput.write(Utils.textPadding("", this.characterSetName, this.header.fieldArray[j].getFieldLength()));
}
break;
case DATE:
if (objectArray[j] != null) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime((Date) objectArray[j]);
StringBuffer t_sb = new StringBuffer();
dataOutput.write(String.valueOf(calendar.get(Calendar.YEAR)).getBytes());
dataOutput.write(Utils.textPadding(String.valueOf(calendar.get(Calendar.MONTH) + 1), this.characterSetName, 2, Utils.ALIGN_RIGHT, (byte) '0'));
dataOutput.write(Utils.textPadding(String.valueOf(calendar.get(Calendar.DAY_OF_MONTH)), this.characterSetName, 2, Utils.ALIGN_RIGHT, (byte) '0'));
} else {
dataOutput.write(" ".getBytes());
}
break;
case FLOAT:
if (objectArray[j] != null) {
dataOutput.write(Utils.doubleFormating((Double) objectArray[j], this.characterSetName, this.header.fieldArray[j].getFieldLength(), this.header.fieldArray[j].getDecimalCount()));
} else {
dataOutput.write(Utils.textPadding("?", this.characterSetName, this.header.fieldArray[j].getFieldLength(), Utils.ALIGN_RIGHT));
}
break;
case NUMERIC:
if (objectArray[j] != null) {
dataOutput.write(
Utils.doubleFormating((Double) objectArray[j], this.characterSetName, this.header.fieldArray[j].getFieldLength(), this.header.fieldArray[j].getDecimalCount()));
} else {
dataOutput.write(
Utils.textPadding("?", this.characterSetName, this.header.fieldArray[j].getFieldLength(), Utils.ALIGN_RIGHT));
}
break;
case BOOLEAN:
if (objectArray[j] != null) {
if ((Boolean) objectArray[j] == Boolean.TRUE) {
dataOutput.write((byte) 'T');
} else {
dataOutput.write((byte) 'F');
}
} else {
dataOutput.write((byte) '?');
}
break;
case MEMO:
break;
default:
throw new DBFException("Unknown field type " + this.header.fieldArray[j].getDataType());
}
}
/*
* iterating through the fields
*/
}
}