[Home] Restricted access for guests. The link to Java source code is disabled
Java source code of 'jhplot.io.db.BaseRecordsFile'
package jhplot.io.db;
import java.io.*;
import java.util.*;
public abstract class BaseRecordsFile {
// The database file.
private RandomAccessFile file;
// Current file pointer to the start of the record data.
protected long dataStartPtr;
// Total length in bytes of the global database headers.
protected static final int FILE_HEADERS_REGION_LENGTH = 16;
// Number of bytes in the record header.
protected static final int RECORD_HEADER_LENGTH = 16;
// The length of a key in the index.
protected static final int MAX_KEY_LENGTH = 64;
// The total length of one index entry - the key length plus the record header length.
protected static final int INDEX_ENTRY_LENGTH = MAX_KEY_LENGTH + RECORD_HEADER_LENGTH;
// File pointer to the num records header.
protected static final long NUM_RECORDS_HEADER_LOCATION = 0;
// File pointer to the data start pointer header.
protected static final long DATA_START_HEADER_LOCATION = 4;
/**
* Creates a new database file, initializing the appropriate headers. Enough space is allocated in
* the index for the specified initial size.
*/
protected BaseRecordsFile(String dbPath, int initialSize) throws IOException, RecordsFileException {
File f = new File(dbPath);
if (f.exists()) {
throw new RecordsFileException("Database already exits: " + dbPath);
}
file = new RandomAccessFile(f, "rw");
dataStartPtr = indexPositionToKeyFp(initialSize); // Record Data Region starts were the
setFileLength(dataStartPtr); // (i+1)th index entry would start.
writeNumRecordsHeader(0);
writeDataStartPtrHeader(dataStartPtr);
}
/**
* Opens an existing database file and initializes the dataStartPtr. The accessFlags
* parameter can be "r" or "rw" -- as defined in RandomAccessFile.
*/
protected BaseRecordsFile(String dbPath, String accessFlags) throws IOException, RecordsFileException {
File f = new File (dbPath);
if(!f.exists()) {
throw new RecordsFileException("Database not found: " + dbPath);
}
file = new RandomAccessFile(f, accessFlags);
dataStartPtr = readDataStartHeader();
}
/**
* Returns an Enumeration of the keys of all records in the database.
*/
public abstract Enumeration enumerateKeys();
/**
* Returns the number or records in the database.
*/
public abstract int getNumRecords();
/**
* Checks there is a record with the given key.
*/
public abstract boolean recordExists(String key);
/**
* Maps a key to a record header.
*/
protected abstract RecordHeader keyToRecordHeader(String key) throws RecordsFileException;
/**
* Locates space for a new record of dataLength size and initializes a RecordHeader.
*/
protected abstract RecordHeader allocateRecord(String key, int dataLength) throws RecordsFileException, IOException;
/**
* Returns the record to which the target file pointer belongs - meaning the specified location
* in the file is part of the record data of the RecordHeader which is returned. Returns null if
* the location is not part of a record. (O(n) mem accesses)
*/
protected abstract RecordHeader getRecordAt(long targetFp) throws RecordsFileException;
protected long getFileLength() throws IOException {
return file.length();
}
protected void setFileLength(long l) throws IOException {
file.setLength(l);
}
/**
* Reads the number of records header from the file.
*/
protected int readNumRecordsHeader() throws IOException {
file.seek(NUM_RECORDS_HEADER_LOCATION);
return file.readInt();
}
/**
* Writes the number of records header to the file.
*/
protected void writeNumRecordsHeader(int numRecords) throws IOException {
file.seek(NUM_RECORDS_HEADER_LOCATION);
file.writeInt(numRecords);
}
/**
* Reads the data start pointer header from the file.
*/
protected long readDataStartHeader() throws IOException {
file.seek(DATA_START_HEADER_LOCATION);
return file.readLong();
}
/**
* Writes the data start pointer header to the file.
*/
protected void writeDataStartPtrHeader(long dataStartPtr) throws IOException {
file.seek(DATA_START_HEADER_LOCATION);
file.writeLong(dataStartPtr);
}
/**
* Returns a file pointer in the index pointing to the first byte
* in the key located at the given index position.
*/
protected long indexPositionToKeyFp(int pos) {
return FILE_HEADERS_REGION_LENGTH + (INDEX_ENTRY_LENGTH * pos);
}
/**
* Returns a file pointer in the index pointing to the first byte
* in the record pointer located at the given index position.
*/
long indexPositionToRecordHeaderFp(int pos) {
return indexPositionToKeyFp(pos) + MAX_KEY_LENGTH;
}
/**
* Reads the ith key from the index.
*/
String readKeyFromIndex(int position) throws IOException {
file.seek(indexPositionToKeyFp(position));
return file.readUTF();
}
/**
* Reads the ith record header from the index.
*/
RecordHeader readRecordHeaderFromIndex(int position) throws IOException {
file.seek(indexPositionToRecordHeaderFp(position));
return RecordHeader.readHeader(file);
}
/**
* Writes the ith record header to the index.
*/
protected void writeRecordHeaderToIndex(RecordHeader header) throws IOException {
file.seek(indexPositionToRecordHeaderFp(header.indexPosition));
header.write(file);
}
/**
* Appends an entry to end of index. Assumes that insureIndexSpace() has already been called.
*/
protected void addEntryToIndex(String key, RecordHeader newRecord, int currentNumRecords) throws IOException, RecordsFileException {
DbByteArrayOutputStream temp = new DbByteArrayOutputStream(MAX_KEY_LENGTH);
(new DataOutputStream(temp)).writeUTF(key);
if (temp.size() > MAX_KEY_LENGTH) {
throw new RecordsFileException("Key is larger than permitted size of " + MAX_KEY_LENGTH + " bytes");
}
file.seek(indexPositionToKeyFp(currentNumRecords));
temp.writeTo(file);
file.seek(indexPositionToRecordHeaderFp(currentNumRecords));
newRecord.write(file);
newRecord.setIndexPosition(currentNumRecords);
writeNumRecordsHeader(currentNumRecords+1);
}
/**
* Removes the record from the index. Replaces the target with the entry at the
* end of the index.
*/
protected void deleteEntryFromIndex(String key, RecordHeader header, int currentNumRecords) throws IOException, RecordsFileException {
if (header.indexPosition != currentNumRecords -1) {
String lastKey = readKeyFromIndex(currentNumRecords-1);
RecordHeader last = keyToRecordHeader(lastKey);
last.setIndexPosition(header.indexPosition);
file.seek(indexPositionToKeyFp(last.indexPosition));
file.writeUTF(lastKey);
file.seek(indexPositionToRecordHeaderFp(last.indexPosition));
last.write(file);
}
writeNumRecordsHeader(currentNumRecords-1);
}
/**
* Adds the given record to the database.
*/
public synchronized void insertRecord(RecordWriter rw) throws RecordsFileException, IOException {
String key = rw.getKey();
if (recordExists(key)) {
throw new RecordsFileException("Key exists: " + key);
}
insureIndexSpace(getNumRecords() + 1);
RecordHeader newRecord = allocateRecord(key, rw.getDataLength());
writeRecordData(newRecord, rw);
addEntryToIndex(key, newRecord, getNumRecords());
}
/**
* Updates an existing record. If the new contents do not fit in the original record,
* then the update is handled by deleting the old record and adding the new.
*/
public synchronized void updateRecord(RecordWriter rw) throws RecordsFileException, IOException {
RecordHeader header = keyToRecordHeader(rw.getKey());
if (rw.getDataLength() > header.dataCapacity) {
deleteRecord(rw.getKey());
insertRecord(rw);
} else {
writeRecordData(header, rw);
writeRecordHeaderToIndex(header);
}
}
/**
* Reads a record.
*/
public synchronized RecordReader readRecord(String key) throws RecordsFileException, IOException {
byte[] data = readRecordData(key);
return new RecordReader(key, data);
}
/**
* Reads the data for the record with the given key.
*/
protected byte[] readRecordData(String key) throws IOException, RecordsFileException {
return readRecordData(keyToRecordHeader(key));
}
/**
* Reads the record data for the given record header.
*/
protected byte[] readRecordData(RecordHeader header) throws IOException {
byte[] buf = new byte[header.dataCount];
file.seek(header.dataPointer);
file.readFully(buf);
return buf;
}
/**
* Updates the contents of the given record. A RecordsFileException is thrown if the new data does not
* fit in the space allocated to the record. The header's data count is updated, but not
* written to the file.
*/
protected void writeRecordData(RecordHeader header, RecordWriter rw) throws IOException, RecordsFileException {
if (rw.getDataLength() > header.dataCapacity) {
throw new RecordsFileException ("Record data does not fit");
}
header.dataCount = rw.getDataLength();
file.seek(header.dataPointer);
rw.writeTo((DataOutput)file);
}
/**
* Updates the contents of the given record. A RecordsFileException is thrown if the new data does not
* fit in the space allocated to the record. The header's data count is updated, but not
* written to the file.
*/
protected void writeRecordData(RecordHeader header, byte[] data) throws IOException, RecordsFileException {
if (data.length > header.dataCapacity) {
throw new RecordsFileException ("Record data does not fit");
}
header.dataCount = data.length;
file.seek(header.dataPointer);
file.write(data, 0, data.length);
}
/**
* Deletes a record.
*/
public synchronized void deleteRecord(String key) throws RecordsFileException, IOException {
RecordHeader delRec = keyToRecordHeader(key);
int currentNumRecords = getNumRecords();
if (getFileLength() == delRec.dataPointer + delRec.dataCapacity) {
// shrink file since this is the last record in the file
setFileLength(delRec.dataPointer);
} else {
RecordHeader previous = getRecordAt(delRec.dataPointer -1);
if (previous != null) {
// append space of deleted record onto previous record
previous.dataCapacity += delRec.dataCapacity;
writeRecordHeaderToIndex(previous);
} else {
// target record is first in the file and is deleted by adding its space to
// the second record.
RecordHeader secondRecord = getRecordAt(delRec.dataPointer + (long)delRec.dataCapacity);
byte[] data = readRecordData(secondRecord);
secondRecord.dataPointer = delRec.dataPointer;
secondRecord.dataCapacity += delRec.dataCapacity;
writeRecordData(secondRecord, data);
writeRecordHeaderToIndex(secondRecord);
}
}
deleteEntryFromIndex(key, delRec, currentNumRecords);
}
// Checks to see if there is space for and additional index entry. If
// not, space is created by moving records to the end of the file.
protected void insureIndexSpace(int requiredNumRecords) throws RecordsFileException, IOException {
int originalFirstDataCapacity;
int currentNumRecords = getNumRecords();
long endIndexPtr = indexPositionToKeyFp(requiredNumRecords);
if (endIndexPtr > getFileLength() && currentNumRecords == 0) {
setFileLength(endIndexPtr);
dataStartPtr = endIndexPtr;
writeDataStartPtrHeader(dataStartPtr);
return;
}
while (endIndexPtr > dataStartPtr) {
RecordHeader first = getRecordAt(dataStartPtr);
byte[] data = readRecordData(first);
first.dataPointer = getFileLength();
// If first.dataCapacity is set to the actual data count BEFORE resetting dataStartPtr,
// and there is free space in 'first', then dataStartPtr will not be reset to the start
// of the second record. Capture the capacity first and use it to perform the reset.
originalFirstDataCapacity = first.dataCapacity;
first.dataCapacity = data.length;
setFileLength(first.dataPointer + data.length);
writeRecordData(first, data);
writeRecordHeaderToIndex(first);
dataStartPtr += originalFirstDataCapacity;
writeDataStartPtrHeader(dataStartPtr);
}
}
/**
* Closes the file.
*/
public synchronized void close() throws IOException, RecordsFileException {
try {
file.close();
} finally {
file = null;
}
}
}