RecordStoreImplpublic class RecordStoreImpl extends Object implements AbstractRecordStoreImplA class implementing a MIDP a record store. |
Fields Summary |
---|
private byte[] | compactBufferused to compact the records of the record store | static final int | AUTHMODE_ANY_ROInternal indicator for AUTHMODE_ANY with read only access
AUTHMODE_ANY_RO has a value of 2. | int | suiteIdunique id for suite that owns this record store | Object | rsLocklock used to synchronize this record store | byte[] | dbHeaderdata block header stored here | private RecordStoreIndex | dbIndexrecord store index | private RecordStoreFile | dbFilerecord store data |
Constructors Summary |
---|
private RecordStoreImpl(int suiteId, String recordStoreName, boolean create)Creates a RecordStoreImpl instance; for internal use only.
Callers from outside must use openRecordStore() .
this.suiteId = suiteId;
rsLock = new Object();
boolean exists = RecordStoreUtil.exists(suiteId, recordStoreName,
RecordStoreFile.DB_EXTENSION);
// Check for errors between app and record store existance.
if (!create && !exists) {
throw new RecordStoreNotFoundException("cannot find record "
+ "store file");
}
/*
* If a new RecordStoreImpl will be created in storage,
* check to see if the space required is available.
*/
if (create && !exists) {
int space = RecordStoreFile.spaceAvailableNewRecordStore(suiteId);
if (space - DB_HEADER_SIZE < 0) {
throw new RecordStoreFullException();
}
}
// Create a RecordStoreFile for storing the record store.
try {
dbFile = new RecordStoreFile(suiteId, recordStoreName,
RecordStoreFile.DB_EXTENSION);
// allocate a new header
dbHeader = new byte[DB_HEADER_SIZE];
if (exists) {
// load header
dbFile.read(dbHeader);
/*
* Verify that the file is actually a record store
* by verifying the record store "signature."
*/
for (int i = 0; i < DB_SIGNATURE.length; i++) {
if (dbHeader[i] != DB_SIGNATURE[i]) {
throw new RecordStoreException("invalid record "+
"store contents");
}
}
} else {
// initialize the header
for (int i = 0; i < DB_SIGNATURE.length; i++) {
dbHeader[i] = DB_SIGNATURE[i];
}
RecordStoreUtil.putInt(1, dbHeader, RS2_NEXT_ID);
RecordStoreUtil.putLong(System.currentTimeMillis(), dbHeader,
RS5_LAST_MODIFIED);
// write the header to the file
dbFile.write(dbHeader);
dbFile.commitWrite();
}
// create the index object
dbIndex = new RecordStoreIndex(this, suiteId, recordStoreName);
} catch (java.io.IOException ioe) {
try {
if (dbFile != null) {
dbFile.close();
}
} catch (java.io.IOException ioe2) {
// ignore exception within exception block
}
if (!exists) {
// avoid preserving just created damaged files
RecordStoreUtil.quietDeleteFile(suiteId,
recordStoreName, RecordStoreFile.DB_EXTENSION);
RecordStoreIndex.deleteIndex(suiteId, recordStoreName);
}
dbFile = null;
throw new RecordStoreException("error opening record store " +
"file");
}
|
Methods Summary |
---|
private int | addBlock(int recordId, byte[] data, int offset, int numBytes)Adds a block for the record and data or sets an existing block to
the data. Splits an exiting block if needed.
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"addBlock("+recordId+") numBytes="+numBytes);
}
// calculate the size of the block needed
int blockSize = RecordStoreUtil.calculateBlockSize(numBytes);
int blockOffset = 0;
int freeBlocksSize = RecordStoreUtil.getInt(dbHeader, RS7_FREE_SIZE);
// initialize the block header
byte[] header = new byte[BLOCK_HEADER_SIZE];
// check if there is the potential for a large enough free block
if (freeBlocksSize >= blockSize) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"blockSize = " + blockSize +
"free size = " + freeBlocksSize);
}
// initialize the number of bytes and search for an
// available free block
RecordStoreUtil.putInt(numBytes, header, 4);
blockOffset = dbIndex.getFreeBlock(header);
}
// set the recordId in to the block header
RecordStoreUtil.putInt(recordId, header, 0);
if (blockOffset > 0) {
// search found a block, use it
splitBlock(blockOffset, header, data, offset, numBytes);
} else {
// search failed, add a new block to the end of the db file
int spaceAvailable = getSizeAvailable();
/**
* spaceAvailable returns the smaller number of total space
* available and the storage limit per suite. If it is less than
* the block size to be added, RecordStoreFullException is thrown
*/
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"spaceAvailable = "+spaceAvailable);
}
// Is there room to grow the file?
if (spaceAvailable < blockSize) {
// Is there enough room totally: in storage and free blocks?
if (spaceAvailable + freeBlocksSize < blockSize) {
throw new RecordStoreFullException();
}
compactRecords();
}
blockOffset = getSize();
// seek to the location and write the block and header
RecordStoreUtil.putInt(numBytes, header, 4);
writeBlock(blockOffset, header, data, offset, numBytes);
// update the db data size
RecordStoreUtil.putInt(RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE) + blockSize,
dbHeader, RS6_DATA_SIZE);
dbFile.seek(RS6_DATA_SIZE);
dbFile.write(dbHeader, RS6_DATA_SIZE, 4);
// dbFile.commitWrite();
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"blockOffset = "+blockOffset);
}
}
return blockOffset;
| public int | addRecord(byte[] data, int offset, int numBytes)Adds a new record to the record store. The recordId for this
new record is returned. This is a blocking atomic operation.
The record is written to persistent storage before the
method returns.
synchronized (rsLock) {
int recordId = getNextRecordID();
try {
// add a block for this record
addBlock(recordId, data, offset, numBytes);
// update the db header
RecordStoreUtil.putInt(recordId+1, dbHeader, RS2_NEXT_ID);
RecordStoreUtil.putInt(getNumRecords()+1, dbHeader, RS3_NUM_LIVE);
RecordStoreUtil.putInt(getVersion()+1, dbHeader, RS4_VERSION);
RecordStoreUtil.putLong(System.currentTimeMillis(), dbHeader,
RS5_LAST_MODIFIED);
// write out the changes to the db header
dbFile.seek(RS2_NEXT_ID);
dbFile.write(dbHeader, RS2_NEXT_ID, 3*4+8);
// dbFile.commitWrite();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing new record "
+ "data");
}
// Return the new record id
return recordId;
}
| public void | closeRecordStore()This method is called when the MIDlet requests to have the
record store closed. Note that the record store will not
actually be closed until closeRecordStore() is called as many
times as openRecordStore() was called. In other words, the
MIDlet needs to make a balanced number of close calls as open
calls before the record store is closed.
When the record store is closed, all listeners are removed
and all RecordEnumerations associated with it become invalid.
If the MIDlet attempts to perform
operations on the RecordStore object after it has been closed,
the methods will throw a RecordStoreNotOpenException.
synchronized (rsLock) {
try {
// close native fd
compactRecords(); // compact before close
dbFile.close();
dbIndex.close();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error closing .db file. "
+ ioe);
} finally {
dbFile = null;
}
}
| private void | compactRecords()Remove free blocks from the record store and compact records
with data into as small a space in dbFile as
possible. Operates from smallest to greatest offset in
dbFile , copying data in chunks towards the
beginning of the file, and updating record store meta-data
as it progresses.
Warning: This is a slow operation that scales linearly
with dbFile size.
// check if the db can be compacted
if (RecordStoreUtil.getInt(dbHeader, RS7_FREE_SIZE) == 0) {
// no free space to compact
return;
}
byte[] header = new byte[BLOCK_HEADER_SIZE];
int currentId = 0;
int currentOffset = RecordStoreImpl.DB_HEADER_SIZE;
int currentSize = 0;
int moveUpNumBytes = 0;
// search through the data blocks for a free block that is large enough
while (currentOffset < getSize()) {
// seek to the next offset
dbFile.seek(currentOffset);
// read the block header
if (dbFile.read(header) != RecordStoreImpl.BLOCK_HEADER_SIZE) {
// could not read the block
throw new IOException();
}
currentId = RecordStoreUtil.getInt(header, 0);
currentSize =
RecordStoreUtil.calculateBlockSize(RecordStoreUtil.getInt(
header, 4));
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"currentId = " + currentId +
" currentSize = " + currentSize);
}
// check if this is a free block or a record
if (currentId < 0) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"found free block at offset " +
currentOffset);
}
// a free block, add to the moveUpNumBytes
moveUpNumBytes += currentSize;
// remove from the index
dbIndex.removeBlock(currentOffset, header);
} else {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"found record block at offset " +
currentOffset);
}
// a record data block, check if it needs to be moved up
if (moveUpNumBytes > 0) {
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"moveUpNumBytes = " + currentOffset);
}
int numMoved = 0;
while (numMoved < currentSize) {
int curRead = currentSize - numMoved;
if (curRead > COMPACT_BUFFER_SIZE) {
curRead = COMPACT_BUFFER_SIZE;
}
dbFile.seek(currentOffset + numMoved);
curRead = dbFile.read(compactBuffer, 0, curRead);
if (curRead == -1) {
throw new IOException();
}
dbFile.seek(currentOffset + numMoved - moveUpNumBytes);
dbFile.write(compactBuffer, 0, curRead);
// dbFile.commitWrite();
numMoved += curRead;
}
dbIndex.updateBlock(currentOffset - moveUpNumBytes, header);
}
}
// added the block size to the currentOffset
currentOffset += currentSize;
}
// check if the db file can be truncated
if (moveUpNumBytes > 0) {
RecordStoreUtil.putInt(
RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE) -
moveUpNumBytes, dbHeader, RS6_DATA_SIZE);
RecordStoreUtil.putInt(0, dbHeader, RS7_FREE_SIZE);
dbFile.seek(RS6_DATA_SIZE);
dbFile.write(dbHeader, RS6_DATA_SIZE, 4+4);
// dbFile.commitWrite();
dbFile.truncate(getSize());
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"compactRecords, truncate to size " +
getSize());
}
}
| public AbstractRecordStoreFile | createIndexFile(int suiteId, java.lang.String name)Creates data base index file associated with this record store
return new RecordStoreFile(suiteId, name,
AbstractRecordStoreFile.IDX_EXTENSION);
| public void | deleteRecord(int recordId)The record is deleted from the record store. The recordId for
this record is NOT reused.
synchronized (rsLock) {
try {
byte[] header = new byte[BLOCK_HEADER_SIZE];
int blockOffset = dbIndex.getRecordHeader(recordId, header);
// free the block
freeBlock(blockOffset, header);
// update the db index
dbIndex.deleteRecordIndex(recordId);
// update the db header
RecordStoreUtil.putInt(getNumRecords()-1, dbHeader, RS3_NUM_LIVE);
RecordStoreUtil.putInt(getVersion()+1, dbHeader, RS4_VERSION);
RecordStoreUtil.putLong(System.currentTimeMillis(), dbHeader,
RS5_LAST_MODIFIED);
// save the updated db header
dbFile.seek(RS3_NUM_LIVE);
dbFile.write(dbHeader, RS3_NUM_LIVE, 2*4+8);
// dbFile.commitWrite();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error updating file after" +
" record deletion");
}
}
| public static void | deleteRecordStore(com.sun.midp.security.SecurityToken token, int suiteId, java.lang.String recordStoreName)Deletes the named record store. MIDlet suites are only allowed
to delete their own record stores. If the named record store is
open (by a MIDlet in this suite or a MIDlet in a different
MIDlet suite) when this method is called, a
RecordStoreException will be thrown. If the named record store
does not exist a RecordStoreNotFoundException will be
thrown. Calling this method does NOT result in recordDeleted
calls to any registered listeners of this RecordStore.
token.checkIfPermissionAllowed(Permissions.MIDP);
// check if file exists and delete it
if (RecordStoreUtil.exists(suiteId, recordStoreName,
RecordStoreFile.DB_EXTENSION)) {
boolean success = RecordStoreIndex.deleteIndex(
suiteId, recordStoreName);
RecordStoreUtil.deleteFile(suiteId,
recordStoreName, RecordStoreFile.DB_EXTENSION);
if (!success) {
throw new RecordStoreException("deleteRecordStore " +
"failed");
}
} else {
throw new RecordStoreNotFoundException("deleteRecordStore " +
"error: file " +
"not found");
}
| private void | freeBlock(int blockOffset, byte[] header)Mark the block at the given offset in db file as free.
int dataSize = RecordStoreUtil.getInt(header, 4);
// calculate the size of the block
int blockSize = RecordStoreUtil.calculateBlockSize(dataSize);
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"freeBlock(" + blockOffset + ") size = " +
blockSize);
}
// mark the block as free
RecordStoreUtil.putInt(-1, header, 0);
RecordStoreUtil.putInt(blockSize - BLOCK_HEADER_SIZE, header, 4);
// save the updated block header
writeBlock(blockOffset, header, null, 0, 0);
// add to the db free size
RecordStoreUtil.putInt(RecordStoreUtil.getInt(dbHeader, RS7_FREE_SIZE) + blockSize,
dbHeader, RS7_FREE_SIZE);
dbFile.seek(RS7_FREE_SIZE);
dbFile.write(dbHeader, RS7_FREE_SIZE, 4);
// dbFile.commitWrite();
| public int | getAuthMode()Get the authorization mode for this record store.
return RecordStoreUtil.getInt(dbHeader, RS1_AUTHMODE);
| public AbstractRecordStoreFile | getDbFile()Returns data base file associated with this record store
return dbFile;
| public long | getLastModified()Returns the last time the record store was modified, in the
format used by System.currentTimeMillis().
return RecordStoreUtil.getLong(dbHeader, RS5_LAST_MODIFIED);
| public int | getNextRecordID()Returns the recordId of the next record to be added to the
record store. This can be useful for setting up pseudo-relational
relationships. That is, if you have two or more
record stores whose records need to refer to one another, you can
predetermine the recordIds of the records that will be created
in one record store, before populating the fields and allocating
the record in another record store. Note that the recordId returned
is only valid while the record store remains open and until a call
to addRecord() .
return RecordStoreUtil.getInt(dbHeader, RS2_NEXT_ID);
| public int | getNumRecords()Returns the number of records currently in the record store.
return RecordStoreUtil.getInt(dbHeader, RS3_NUM_LIVE);
| public int | getRecord(int recordId, byte[] buffer, int offset)Returns the data stored in the given record.
synchronized (rsLock) {
try {
byte[] header = new byte[BLOCK_HEADER_SIZE];
int blockOffset = dbIndex.getRecordHeader(recordId, header);
int dataSize = RecordStoreUtil.getInt(header, 4);
dbFile.seek(blockOffset+BLOCK_HEADER_SIZE);
return dbFile.read(buffer, offset, dataSize);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
}
| public byte[] | getRecord(int recordId)Returns a copy of the data stored in the given record.
synchronized (rsLock) {
try {
byte[] header = new byte[BLOCK_HEADER_SIZE];
int blockOffset = dbIndex.getRecordHeader(recordId, header);
int dataSize = RecordStoreUtil.getInt(header, 4);
if (dataSize == 0) {
return null;
}
byte[] buffer = new byte[dataSize];
dbFile.seek(blockOffset+BLOCK_HEADER_SIZE);
dbFile.read(buffer);
return buffer;
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
}
| public int[] | getRecordIDs()Returns all of the recordId's currently in the record store.
synchronized (rsLock) {
return dbIndex.getRecordIDs();
}
| public int | getRecordSize(int recordId)Returns the size (in bytes) of the MIDlet data available
in the given record.
synchronized (rsLock) {
byte[] header = new byte[BLOCK_HEADER_SIZE];
try {
dbIndex.getRecordHeader(recordId, header);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
return RecordStoreUtil.getInt(header, 4);
}
| public int | getSize()Returns the amount of space, in bytes, that the record store
occupies. The size returned includes any overhead associated
with the implementation, such as the data structures
used to hold the state of the record store, etc.
return DB_HEADER_SIZE + RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE);
| public int | getSizeAvailable()Returns the amount of additional room (in bytes) available for
this record store to grow. Note that this is not necessarily
the amount of extra MIDlet-level data which can be stored,
as implementations may store additional data structures with
each record to support integration with native applications,
synchronization, etc.
int fileSpace = dbFile.spaceAvailable(suiteId) -
BLOCK_HEADER_SIZE - DB_HEADER_SIZE;
int limitSpace = RMSConfig.STORAGE_SUITE_LIMIT -
RecordStoreUtil.getInt(dbHeader, RS6_DATA_SIZE) -
BLOCK_HEADER_SIZE - DB_HEADER_SIZE;
int rv = (fileSpace < limitSpace) ? fileSpace : limitSpace;
return (rv < 0) ? 0 : rv;
| public int | getVersion()Each time a record store is modified (by
addRecord , setRecord , or
deleteRecord methods) its version is
incremented. This can be used by MIDlets to quickly tell if
anything has been modified.
The initial version number is implementation dependent.
The increment is a positive integer greater than 0.
The version number increases only when the RecordStore is updated.
The increment value need not be constant and may vary with each
update.
return RecordStoreUtil.getInt(dbHeader, RS4_VERSION);
| public static java.lang.String[] | listRecordStores(com.sun.midp.security.SecurityToken token, int suiteId)Returns an array of the names of record stores owned by the
MIDlet suite. Note that if the MIDlet suite does not
have any record stores, this function will return null.
The order of RecordStore names returned is implementation
dependent.
token.checkIfPermissionAllowed(Permissions.MIDP);
return RecordStoreFile.listRecordStores(suiteId);
| public static com.sun.midp.rms.RecordStoreImpl | openRecordStore(com.sun.midp.security.SecurityToken token, int suiteId, java.lang.String recordStoreName, boolean createIfNecessary)Open (and possibly create) a record store associated with the
given MIDlet suite. If this method is called by a MIDlet when
the record store is already open by a MIDlet in the MIDlet suite,
this method returns a reference to the same RecordStoreImpl object.
token.checkIfPermissionAllowed(Permissions.MIDP);
return new RecordStoreImpl(suiteId, recordStoreName,
createIfNecessary);
| public void | setMode(int authmode, boolean writable)Changes the access mode for this RecordStore. The authorization
mode choices are:
AUTHMODE_PRIVATE - Only allows the MIDlet
suite that created the RecordStore to access it. This
case behaves identically to
openRecordStore(recordStoreName,
createIfNecessary) .
AUTHMODE_ANY - Allows any MIDlet to access the
RecordStore. Note that this makes your recordStore
accessible by any other MIDlet on the device. This
could have privacy and security issues depending on
the data being shared. Please use carefully.
The owning MIDlet suite may always access the RecordStore and
always has access to write and update the store. Only the
owning MIDlet suite can change the mode of a RecordStore.
synchronized (rsLock) {
int newAuthMode = authmode;
if ((authmode == RecordStore.AUTHMODE_ANY) &&
(writable == false)) {
newAuthMode = AUTHMODE_ANY_RO;
}
RecordStoreUtil.putInt(newAuthMode, dbHeader, RS1_AUTHMODE);
try {
// write out the changes to the db header
dbFile.seek(RS1_AUTHMODE);
dbFile.write(dbHeader, RS1_AUTHMODE, 4);
// dbFile.commitWrite();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing record " +
"store attributes");
}
}
| public void | setRecord(int recordId, byte[] newData, int offset, int numBytes)Sets the data in the given record to that passed in. After
this method returns, a call to getRecord(int recordId)
will return an array of numBytes size containing the data
supplied here.
synchronized (rsLock) {
try {
byte[] header = new byte[BLOCK_HEADER_SIZE];
int blockOffset = dbIndex.getRecordHeader(recordId, header);
int oldBlockSize =
RecordStoreUtil.calculateBlockSize(RecordStoreUtil.getInt(
header, 4));
int newBlockSize = RecordStoreUtil.calculateBlockSize(numBytes);
if (newBlockSize <= oldBlockSize) {
// reuse the old block
splitBlock(blockOffset, header, newData, offset, numBytes);
} else {
// free the old record data
freeBlock(blockOffset, header);
// add a block that contains the new record data
addBlock(recordId, newData, offset, numBytes);
}
// update the db header
RecordStoreUtil.putInt(getVersion()+1, dbHeader, RS4_VERSION);
RecordStoreUtil.putLong(System.currentTimeMillis(), dbHeader,
RS5_LAST_MODIFIED);
// write out the changes to the db header
dbFile.seek(RS4_VERSION);
dbFile.write(dbHeader, RS4_VERSION, 4+8);
// dbFile.commitWrite();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error setting record data");
}
}
| private void | splitBlock(int blockOffset, byte[] header, byte[] newData, int offset, int numBytes)Set the record in the block to the data passed in and adds any remaining
space to the free list.
if (Logging.REPORT_LEVEL <= Logging.INFORMATION) {
Logging.report(Logging.INFORMATION, LogChannels.LC_RMS,
"splitBlock("+RecordStoreUtil.getInt(header, 0) +
") old numBytes = " + RecordStoreUtil.getInt(header, 4) +
" new numBytes=" + numBytes);
}
// calculate the size of the block
int oldBlockSize =
RecordStoreUtil.calculateBlockSize(RecordStoreUtil.getInt(
header, 4));
// calculate the size of the block
int newBlockSize = RecordStoreUtil.calculateBlockSize(numBytes);
// update the block header
RecordStoreUtil.putInt(numBytes, header, 4);
// seek to the location and write the block and header
writeBlock(blockOffset, header, newData, offset, numBytes);
// check if there is any left over free space
int freeSize = oldBlockSize - newBlockSize - BLOCK_HEADER_SIZE;
if (freeSize >= 0) {
// update the block header and free block of extra space
RecordStoreUtil.putInt(freeSize, header, 4);
freeBlock(blockOffset+newBlockSize, header);
}
| private void | writeBlock(int blockOffset, byte[] header, byte[] data, int offset, int numBytes)Writes a block to the db file at the given offset.
int remainder;
dbFile.seek(blockOffset);
dbFile.write(header);
if (data != null && numBytes > 0) {
dbFile.write(data, offset, numBytes);
remainder = numBytes % BLOCK_HEADER_SIZE;
if (remainder != 0) {
// DB_SIGNATURE used here as meaningless pad bytes
dbFile.write(DB_SIGNATURE, 0, BLOCK_HEADER_SIZE - remainder);
}
}
// flush the writes
// dbFile.commitWrite();
// update the index
dbIndex.updateBlock(blockOffset, header);
|
|