blob: 482240517a58ac8d92e023666790a88e0b1a215a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.stem.gis.dbf;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.input.SwappedDataInputStream;
/**
* Simple implementation of a dBASE v5 and older file reader
*
*/
public class DbfInputStream extends SwappedDataInputStream
{
protected DbfHeader header;
public DbfInputStream(InputStream input) throws IOException
{
super(input);
readHeader();
}
/**
*
* @return The dBASE File Header
*/
public DbfHeader getHeader()
{
return header;
}
/**
* @return If there are more records to read
* @throws IOException
*/
public boolean hasMoreRecords() throws IOException
{
return !atEOF();
}
/**
* Reads the next dBASE table record (row) from a file
* @return The table record or null if no more records exist
* @throws IOException
*/
public DbfRecord readNextRecord() throws IOException
{
if (!hasMoreRecords()) {
return null;
}
DbfRecord record = new DbfRecord();
// Read the record into memory
byte[] buf = new byte[header.recordSize];
readFully(buf);
int bufIdx = 0;
// byte 0 - deleted flag
record.deletedFlag = (buf[bufIdx++] == 0x2A);
// Loop through the field definitions and partition data
for (DbfFieldDef def : header.fieldDefinitions) {
Object dataObject = new String(buf, bufIdx, def.fieldSize).trim();
try {
switch (def.fieldType) {
case 'N': if (def.fieldDecimalCount == 0) {Integer.valueOf((String)dataObject);break;}
case 'F': dataObject = Float.valueOf((String)dataObject);break;
default: break;
}
} catch (Exception e) {
//e.printStackTrace();
}
record.data.add(dataObject);
bufIdx += def.fieldSize;
}
return record;
}
/**
* Reads a dBASE v5 32 byte header
* @throws IOException
*/
protected void readHeader() throws IOException
{
DbfHeader header = new DbfHeader();
byte versionByte = readByte();
header.version = (short)(versionByte & 0xf); // 0 / version
header.dateYear = 1900+readUnsignedByte(); // 1 / YY
header.dateMonth = readUnsignedByte(); // 2 / MM
header.dateDay = readUnsignedByte(); // 3 / DD
header.records = readInt(); // 4-7
header.headerSize = readUnsignedShort(); // 8-9
header.recordSize = readUnsignedShort(); // 10-11
skipBytes(2); // reserved, 12-13
header.incompleteTransactionFlag = readByte(); // 14
header.encryptionFlag = readByte(); // 15
skipBytes(12); // reserved, 16-27
header.prodMdxFlag = readByte(); // 28
header.driverId = readByte(); // 29
// reserved, 30-31
skipBytes(2);
// dBASE v7 Fields (bytes 32-67)
// // 32-63 // driverName
// read(buf, 0, 32);
// header.driveName = new String(buf, 0, 32);
// readInt(); // 64-67 / reserved
while (!atEOH()) {
DbfFieldDef fd = readFieldDescriptor();
header.fieldDefinitions.add(fd);
}
skipBytes(1); // field def boundary (0x0D)
this.header = header;
}
/**
* Reads a 32-byte dBASE Table Field Descriptor
* @return The field descriptor
* @throws IOException
*/
protected DbfFieldDef readFieldDescriptor() throws IOException
{
DbfFieldDef def = new DbfFieldDef();
byte[] buf = new byte[11];
readFully(buf, 0, 11); // 0 - 10 / fieldName
def.fieldName = new String(buf, 0, 11);
def.fieldType = (char)readByte(); // 11 / fieldType
skipBytes(4); // 12-15 / reserved
def.fieldSize = (short)readUnsignedByte(); // 16 / fieldSize
def.fieldDecimalCount = (short)readUnsignedByte(); // 17 / decimalCount
skipBytes(2);// 18-19 / reserved
def.workAreaId = readByte(); // 20 / work area id
skipBytes(10); // 21-30 / reserved
def.mdxFieldFlag = readByte(); // 31 / MDX Flag
return def;
}
/**
* Peeks at next byte to see if it equals the value passed
* @param value Value to check
* @return Whether the next byte to read equals the value passed
* @throws IOException
*/
private boolean nextByteEquals(byte value) throws IOException
{
mark(1);
boolean isEqual = (readByte() == value);
reset();
return isEqual;
}
/**
* Checks if the next byte to read is the end-of-header boundary (0x0d)
* @return If stream is at end of header (EOH) boundary
* @throws IOException
*/
private boolean atEOH() throws IOException
{
return nextByteEquals((byte)0x0d);
}
/**
* Checks if the next byte to read is the end-of-file boundary (0x1a)
* @return If stream is at end-of-file (EOF) boundary
* @throws IOException
*/
private boolean atEOF() throws IOException
{
return nextByteEquals((byte)0x1a);
}
}