// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/dataAccess/shape/input/DbfInputStream.java,v $
// $RCSfile: DbfInputStream.java,v $
// $Revision: 1.14 $
// $Date: 2009/02/05 18:46:11 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.dataAccess.shape.input;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.bbn.openmap.dataAccess.shape.DbfTableModel;
import com.bbn.openmap.util.Debug;
/**
* Reads the contents of a DBF file and provides access to what it has read
* through several get methods
*
* @author Doug Van Auken
*/
public class DbfInputStream {
/**
* An input stream to process primitives in Little Endian or Big Endian
*/
private LittleEndianInputStream _leis = null;
/**
* An array of column names, as read from the field descripter array
*/
private String[] _columnNames = null;
/**
* An array of column lengths, as read from the field descripter array
*/
private int[] _lengths = null;
/**
* An array of decimal counts, as read from the field descripter array
*/
private byte[] _decimalCounts = null;
/**
* An array of column types, as read from the field descripter array
*/
private byte[] _types = null;
/** The number of columns */
private int _columnCount = -1;
/** The number of rows */
private int _rowCount = -1;
/** The header length */
private short _headerLength = -1;
/** The record length */
// private short _recordLength = -1; // Unused
/**
* An ArrayList with each element representing a record, which itself is an
* ArrayList
*/
private List<List<Object>> _records = null;
/**
* Creates a LittleEndianInputStream then uses it to read the contents of the
* DBF file
*
* @param is An InputStream used to create a LittleEndianInputStream
*/
public DbfInputStream(InputStream is)
throws Exception {
BufferedInputStream bis = new BufferedInputStream(is);
_leis = new LittleEndianInputStream(bis);
readHeader();
readFieldDescripters();
readData();
}
/**
* Returns an array of column names
*
* @return An array of column names
*/
public String[] getColumnNames() {
return _columnNames;
}
/**
* Returns an array of character lengths
*
* @return An array of character lengths
*/
public int[] getLengths() {
return _lengths;
}
/**
* Returns an array of decimal counts
*
* @return An array of decimal counts
*/
public byte[] getDecimalCounts() {
return _decimalCounts;
}
/**
* Returns an array of field types
*
* @return An array of field types
*/
public byte[] getTypes() {
return _types;
}
/**
* Returns an ArrayList of records
*
* @return An ArrayList of records
*/
public List<List<Object>> getRecords() {
return _records;
}
/**
* Returns the number of columns
*
* @return The number of columns
*/
public int getColumnCount() {
return _columnCount;
}
/**
* Returns the number of rows
*
* @return The number of rows
*/
public int getRowCount() {
return _rowCount;
}
/**
* Reads the header
*/
private void readHeader()
throws IOException {
/* byte description = */_leis.readByte();
/* byte year = */_leis.readByte();
/* byte month = */_leis.readByte();
/* byte day = */_leis.readByte();
_rowCount = _leis.readLEInt();
_headerLength = _leis.readLEShort();
/* _recordLength = */_leis.readLEShort();
_columnCount = (_headerLength - 32 - 1) / 32;
_leis.skipBytes(20);
}
/**
* Initializes arrays that hold column names, column types, character
* lengths, and decimal counts, then populates them
*/
private void readFieldDescripters()
throws IOException {
_columnNames = new String[_columnCount];
_types = new byte[_columnCount];
_lengths = new int[_columnCount];
_decimalCounts = new byte[_columnCount];
for (int n = 0; n <= _columnCount - 1; n++) {
_columnNames[n] = _leis.readString(11).trim();
//
// Some TIGER dbf files from ESRI have nulls
// in the column names. Delete them.
//
int ix = _columnNames[n].indexOf((char) 0);
if (ix > 0) {
_columnNames[n] = _columnNames[n].substring(0, ix);
}
_types[n] = (byte) _leis.readByte();
_leis.skipBytes(4);
_lengths[n] = _leis.readUnsignedByte();
_decimalCounts[n] = _leis.readByte();
_leis.skipBytes(14);
}
}
/**
* Reads the data and places data in a class scope ArrayList of records
*/
public void readData()
throws IOException {
// Thanks to Bart Jourquin for the heads-up that some locales
// may try to read this data incorrectly. DBF files have to
// have '.' as decimal markers, not ','
DecimalFormat df = new DecimalFormat();
DecimalFormatSymbols dfs = new DecimalFormatSymbols(Locale.ENGLISH);
df.setDecimalFormatSymbols(dfs);
_leis.skipBytes(2);
_records = new ArrayList<List<Object>>(_rowCount);
for (int r = 0; r <= _rowCount - 1; r++) {
ArrayList<Object> record = new ArrayList<Object>(_columnCount);
for (int c = 0; c <= _columnCount - 1; c++) {
int length = _lengths[c];
if (length == -1)
length = 255;
int type = _types[c];
int numDecSpaces = _decimalCounts[c];
df.setMaximumFractionDigits(numDecSpaces);
String cell = _leis.readString(length);
try {
record.add(c, DbfTableModel.getObjectForType(cell, type, df, length));
} catch (ParseException pe) {
if (Debug.debugging("shape")) {
Debug.error("DbfInputStream: error parsing column " + c + ", row " + r + ", expected number and got " + cell);
}
record.add(c, DbfTableModel.appendWhitespaceOrTrim(null, length));
}
}
_records.add(record);
_leis.skipBytes(1);
}
}
}