/*
* DBFExporter.java
*
* Created on April 13, 2007, 2:18 PM
*
*/
package ika.table;
import ika.utils.LittleEndianOutputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.swing.table.TableColumn;
/**
* Exporter for the DBF file format.
* @author Bernhard Jenny, Institute of Cartography, ETH Zurich.
*/
public class DBFExporter implements TableExporter {
private static final int STRING_LENGTH = 64;
private static final int NUMBER_LENGTH = 20; // F n=1..20
private static final int NUMBER_DECIMALS = 8;
/** Creates a new instance of DBFExporter */
public DBFExporter() {
}
public void exportTable(OutputStream outputStream, Table table)
throws IOException {
BufferedOutputStream bos = null;
try {
bos = new BufferedOutputStream(outputStream);
LittleEndianOutputStream dos = new LittleEndianOutputStream(bos);
// dbf flag
dos.write(0x03);
// current date
Calendar cal = GregorianCalendar.getInstance();
int year = cal.get(Calendar.YEAR); // 2002
int month = cal.get(Calendar.MONTH); // 0=Jan, 1=Feb, ...
int day = cal.get(Calendar.DAY_OF_MONTH); // 1...
dos.write(year - 1900);
dos.write(month);
dos.write(day);
// number of records
dos.writeInt(table.getRowCount());
// header size
final int columnsCount = table.getColumnCount();
dos.writeShort(32 + columnsCount * 32 + 1);
// record size
dos.writeShort(this.computeRecordSize(table));
// reserved value, fill with 0
dos.writeShort(0);
// transaction byte
dos.write(0);
// encription byte
dos.write(0);
// multi user environment use
for (int i = 0; i < 13; i++)
dos.write(0);
// codepage / language driver
// ESRI shape files use code 0x57 to indicate that
// data is written in ANSI (whatever that means).
// http://www.esricanada.com/english/support/faqs/arcview/avfaq21.asp
dos.write(0x57);
// two reserved bytes
dos.writeShort(0);
this.writeFieldDescriptors(dos, table);
// header record terminator
dos.write(0x0D);
this.writeRecords(dos, table);
} finally {
if (bos != null)
bos.close();
}
}
private void writeString(LittleEndianOutputStream dos, String str, int length)
throws IOException {
byte[] b = str.getBytes("ISO-8859-1");
dos.write(b, 0, Math.min(length, b.length));
for (int c = b.length; c < length; c++)
dos.write(0);
}
private void writeFieldDescriptors(LittleEndianOutputStream dos, Table table)
throws IOException {
int columnsCount = table.getColumnCount();
for (int i = 0; i < columnsCount; i++) {
TableColumn tc = table.getColumn(i);
// write column title, 10 chars, plus terminating 0.
String title = tc.getHeaderValue().toString().trim();
this.writeString(dos, title, 10);
dos.write(0x0);
// write field type
if (table.isStringColumn(i)) {
dos.write('C');
dos.writeInt(0); // field address (ignored)
dos.write(STRING_LENGTH); // field length
dos.write(0); // decimal count not used
} else if (table.isDoubleColumn(i)) {
dos.write('F');
dos.writeInt(0); // field address (ignored)
dos.write(NUMBER_LENGTH); // field length
dos.write(NUMBER_DECIMALS); // decimal count
} else {
throw new IOException("DBF export: unsupported type");
}
// 14 reserved or unusued bytes
for (int c = 0; c < 14; c++)
dos.write(0);
}
}
private int computeRecordSize(Table table) throws IOException {
int recordSize = 1; // 1 for deletion flag
int columnsCount = table.getColumnCount();
for (int i = 0; i < columnsCount; i++) {
TableColumn tc = table.getColumn(i);
if (table.isStringColumn(i)) {
recordSize += STRING_LENGTH;
} else if (table.isDoubleColumn(i)) {
recordSize += NUMBER_LENGTH;
} else {
throw new IOException("DBF export: unsupported type");
}
}
return recordSize;
}
private void writeRecords(LittleEndianOutputStream dos, Table table)
throws IOException {
int columnsCount = table.getColumnCount();
int rowsCount = table.getRowCount();
for (int row = 0; row < rowsCount; row++) {
// write deleted flag
dos.write(' ');
for (int col = 0; col < columnsCount; col++) {
TableColumn tc = table.getColumn(col);
if (table.isDoubleColumn(col)) {
final Double d = (Double)table.getValueAt(row, col);
String nbrStr = ika.utils.NumberFormatter.format(
d.doubleValue(), NUMBER_LENGTH, NUMBER_DECIMALS);
this.writeString(dos, nbrStr, NUMBER_LENGTH);
} else {
String str = (String)table.getValueAt(row, col);
this.writeString(dos, str, STRING_LENGTH);
}
}
}
}
}