/*
* DbfReader.java
*
* Created on October 11, 2002 Last edited on October 11, 2002
*/
package nl.javel.gisbeans.io.esri;
import java.io.IOException;
import java.io.Serializable;
import java.net.URL;
import java.util.ArrayList;
import nl.javel.gisbeans.io.EndianInterface;
import nl.javel.gisbeans.io.ObjectEndianInputStream;
/**
* This class reads a dbf file (dBaseIII) used in Esri ShapeFiles
* @author Paul Jacobs
* Peter Jacobs
* @since JDK 1
* @version 1
*/
public class DbfReader implements Serializable
{
/** the FBFFile. */
private URL dbfFile;
/** the numberofColumns. */
private int[] columnLength;
/** the names of the columns. */
private String[] columnNames;
/** the headerLength. */
private int headerLength = 0;
/** the number of columns. */
private int numColumns = 0;
/** the number of records. */
private int numRecords = 0;
/** the length of the records. */
private int recordLength = 0;
/** the cachedContent. */
private transient String[][] cachedContent = null;
/** may we cache parsed data?.. */
private boolean cache = true;
/**
* constructs a DbfReader
* @param dbfFile the URL of the dbfFile
* @throws IOException whenever url does not occur to exist.
*/
public DbfReader(URL dbfFile) throws IOException
{
this.dbfFile = dbfFile;
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(dbfFile.openStream());
if (dbfInput.readByte() != 3)
throw new IOException("dbf file does not seem to be a Dbase III file");
dbfInput.skipBytes(3);
dbfInput.setEncode(EndianInterface.LITTLE_ENDIAN);
this.numRecords = dbfInput.readInt();
this.headerLength = dbfInput.readShort();
this.numColumns = (this.headerLength - 33) / 32;
this.recordLength = dbfInput.readShort();
this.columnLength = new int[this.numColumns];
this.columnNames = new String[this.numColumns];
dbfInput.skipBytes(20);
for (int i = 0; i < this.numColumns; i++)
{
StringBuffer buffer = new StringBuffer();
for (int j = 0; j < 10; j++)
{
byte b = dbfInput.readByte();
if (b > 31)
buffer.append((char) b);
}
this.columnNames[i] = buffer.toString();
dbfInput.setEncode(EndianInterface.BIG_ENDIAN);
dbfInput.readChar();
dbfInput.skipBytes(4);
this.columnLength[i] = dbfInput.readByte();
if (this.columnLength[i] < 0)
this.columnLength[i] += 256;
dbfInput.skipBytes(15);
}
dbfInput.close();
}
/**
* returns the columnNames
* @return String[] the columnNames
*/
public String[] getColumnNames()
{
return this.columnNames;
}
/**
* returns the row
* @param rowNumber the rowNumber
* @return String[] the attributes of the row
* @throws IOException on read failure
* @throws IndexOutOfBoundsException whenever the rowNumber > numRecords
*/
public String[] getRow(int rowNumber) throws IOException, IndexOutOfBoundsException
{
if (rowNumber > this.numRecords)
{
throw new IndexOutOfBoundsException("dbfFile : rowNumber > numRecords");
}
// Let's see if we may cache.
if (this.cachedContent != null && this.cache)
{
return this.cachedContent[rowNumber];
}
// Either we may not cache of the cache is still empty
String[] row = new String[this.numColumns];
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(this.dbfFile.openStream());
dbfInput.skipBytes(this.headerLength + 1);
dbfInput.skipBytes(rowNumber * this.recordLength);
for (int i = 0; i < this.numColumns; i++)
{
byte[] bytes = new byte[this.columnLength[i]];
dbfInput.read(bytes);
row[i] = new String(bytes);
}
dbfInput.close();
return row;
}
/**
* returns a table of all attributes stored for the particular dbf-file
* @return String[][] a table of attributes
* @throws IOException an IOException
*/
public String[][] getRows() throws IOException
{
// Let's see if we may cache.
if (this.cachedContent != null && this.cache)
{
return this.cachedContent;
}
String[][] result = new String[this.numRecords][this.numColumns];
ObjectEndianInputStream dbfInput = new ObjectEndianInputStream(this.dbfFile.openStream());
dbfInput.skipBytes(this.headerLength + 1);
for (int row = 0; row < this.numRecords; row++)
{
for (int col = 0; col < this.numColumns; col++)
{
byte[] bytes = new byte[this.columnLength[col]];
dbfInput.read(bytes);
result[row][col] = new String(bytes);
if (col == this.numColumns - 1)
{
dbfInput.skipBytes(1);
}
}
}
dbfInput.close();
if (this.cache)
{
this.cachedContent = result;
}
return result;
}
/**
* returns the array of rowNumbers belonging to a attribute/column pair
* @param attribute the attribute value
* @param columnName the name of the column
* @return int[] the array of shape numbers.
* @throws IOException on read failure
*/
public int[] getRowNumbers(String attribute, String columnName) throws IOException
{
ArrayList result = new ArrayList();
String[][] rows = this.getRows();
for (int col = 0; col < this.numColumns; col++)
{
if (this.columnNames[col].equals(columnName))
{
for (int row = 0; row < this.numRecords; row++)
{
if (rows[row][col].equals(attribute))
result.add(new Integer(row));
}
}
}
int[] array = new int[result.size()];
for (int i = 0; i < array.length; i++)
{
array[i] = ((Integer) result.get(i)).intValue();
}
return array;
}
/**
* may we cache parsed data for a session. If false, every getRows results in IO activity. If true data is stored
* inMemory
* @return Returns the cache.
*/
public boolean isCache()
{
return this.cache;
}
/**
* @param cache The cache to set.
*/
public void setCache(boolean cache)
{
this.cache = cache;
}
}