RecordStorepublic class RecordStore extends Object A class representing a record store. A record store consists of a
collection of records which will remain persistent across multiple
invocations of the MIDlet. The platform is responsible for
making its best effort to maintain the integrity of the
MIDlet's record stores throughout the normal use of the
platform, including reboots, battery changes, etc.
Record stores are created in platform-dependent locations, which
are not exposed to the MIDlets. The naming space for record stores
is controlled at the MIDlet suite granularity. MIDlets within a
MIDlet suite are allowed to create multiple record stores, as long
as they are each given different names. When a MIDlet suite is
removed from a platform all the record stores associated with its
MIDlets will also be removed. MIDlets within a MIDlet suite can
access each other's record stores directly. New APIs in MIDP 2.0
allow for the explicit sharing of record stores if the MIDlet
creating the RecordStore chooses to give such permission.
Sharing is accomplished through the ability to name a
RecordStore created by another MIDlet suite.
RecordStores are uniquely named using the unique name of the
MIDlet suite plus the name of the RecordStore. MIDlet suites are
identified by the MIDlet-Vendor and MIDlet-Name attributes from the
application descriptor.
Access controls are defined when RecordStores to be shared are
created. Access controls are enforced when RecordStores are
opened. The access modes allow private use or shareable
with any other MIDlet suite.
Record store names are case sensitive and may consist of any
combination of between one and 32 Unicode characters
inclusive. Record store names must be unique within the scope of a
given MIDlet suite. In other words, MIDlets within a MIDlet suite
are not allowed to create more than one record store with the same
name, however a MIDlet in one MIDlet suite is allowed to have a
record store with the same name as a MIDlet in another MIDlet
suite. In that case, the record stores are still distinct and
separate.
No locking operations are provided in this API. Record store
implementations ensure that all individual record store operations
are atomic, synchronous, and serialized, so no corruption will
occur with multiple accesses. However, if a MIDlet uses multiple
threads to access a record store, it is the MIDlet's responsibility
to coordinate this access or unintended consequences may result.
Similarly, if a platform performs transparent synchronization of a
record store, it is the platform's responsibility to enforce
exclusive access to the record store between the MIDlet and
synchronization engine.
Records are uniquely identified within a given record store by
their recordId, which is an integer value. This recordId is used as
the primary key for the records. The first record created in a
record store will have recordId equal to one (1). Each subsequent
record added to a RecordStore will be assigned a recordId one
greater than the record added before it. That is, if two records
are added to a record store, and the first has a recordId of 'n',
the next will have a recordId of 'n + 1'. MIDlets can create other
sequences of the records in the RecordStore by using the
RecordEnumeration class.
This record store uses long integers for time/date stamps, in
the format used by System.currentTimeMillis(). The record store is
time stamped with the last time it was modified. The record store
also maintains a version number, which is an integer that
is incremented for each operation that modifies the contents of the
RecordStore. These are useful for synchronization engines as well
as other things. |
Fields Summary |
---|
public static final int | AUTHMODE_PRIVATEAuthorization to allow access only to the current MIDlet
suite. AUTHMODE_PRIVATE has a value of 0. | public static final int | AUTHMODE_ANYAuthorization to allow access to any MIDlet
suites. AUTHMODE_ANY has a value of 1. | private static final int | AUTHMODE_ANY_ROInternal indicator for AUTHMODE_ANY with read only access
AUTHMODE_ANY_RO has a value of 2. | private static final byte[] | DB_INITpre initialized RecordStore header structure | private static final int | SIGNATURE_LENGTHlength of the signature string in bytes | private static final int | DB_RECORD_HEADER_LENGTHsize of a per record meta-data object | private static final int | DB_BLOCK_SIZEstorage space allocated in multiples of DB_BLOCK_SIZE ,
which can not be smaller than DB_RECORD_HEADER_LENGTH and
must be a multiple of DB_RECORD_HEADER_LENGTH | private static final int | DB_COMPACTBUFFER_SIZEsize of the buffer for compacting record store | private static Vector | dbCachecache of open RecordStore instances | private static final Object | dbCacheLocklock to protect static dbcache state | private String | recordStoreNamename of this record store | private String | uniqueIdPathunique storage id for this record store | private int | opencountnumber of open instances of this record store | private com.sun.midp.rms.RecordStoreFile | dbrafRecordStoreFile where this record store is stored | Object | rsLocklock used to synchronize this record store | private Vector | recordListenerrecordListeners of this record store | private RecordHeaderCache | recHeadCachecache of record headers | private static int | CACHE_SIZEnumber of direct mapped cache entries | private static byte[] | recHeadBufstatic buffer used in loading/storing RecordHeader data | private int | dbNextRecordIDnext record's id | private int | dbVersionrecord store version | private int | dbAuthModeauthorization mode of this record store
0: AUTHMODE_PRIVATE, Read/Write
1: AUTHMODE_ANY, Read/Write
2: AUTHMODE_ANY_RO, Read-Only | private int | dbNumLiveRecordscount of live records | private long | dbLastModifiedtime record store was last modified (in milliseconds | private int | dbFirstRecordOffsetoffset of first record | private int | dbFirstFreeBlockOffsetoffset of first free block | private int | dbDataStartoffset of the first data block | private int | dbDataEndoffset of the last data block | private static byte[] | dbStatestatic buffer used in loading/storing dbState | private static final int | RS_SIGNATURERS_SIGNATURE offset | private static final int | RS_NUM_LIVERS_NUM_LIVE offset | private static final int | RS_AUTHMODERS_AUTHMODE offset | private static final int | RS_VERSIONRS_VERSION offset | private static final int | RS_NEXT_IDRS_NEXT_ID offset | private static final int | RS_REC_STARTRS_REC_START offset | private static final int | RS_FREE_STARTRS_FREE_START offset | private static final int | RS_LAST_MODIFIEDRS_LAST_MODIFIED offset | private static final int | RS_DATA_STARTRS_START_OF_DATA offset | private static final int | RS_DATA_ENDRS_END_OF_DATA offset |
Constructors Summary |
---|
private RecordStore()MIDlets must use openRecordStore() to get
a RecordStore object. If this constructor
is not declared (as private scope), Javadoc (and Java)
will assume a public constructor.
| private RecordStore(String uidPath, String recordStoreName, boolean create)Apps must use openRecordStore() to get
a RecordStore object. This constructor
is used internally for creating RecordStore objects.
dbCacheLock must be held before calling
this constructor.
this.recordStoreName = recordStoreName;
this.uniqueIdPath = uidPath;
recHeadCache = new RecordHeaderCache(CACHE_SIZE);
rsLock = new Object();
recordListener = new java.util.Vector(3);
boolean exists = RecordStoreFile.exists(uidPath);
// Check for errors between app and record store existance.
if (!create && !exists) {
throw new RecordStoreNotFoundException("cannot find record "
+ "store file");
}
/*
* If a new RecordStore will be created in storage,
* check to see if the space required is available.
*/
if (create && !exists) {
int space = RecordStoreFile.spaceAvailable();
if (space - DB_INIT.length < 0) {
throw new RecordStoreFullException();
}
}
// Create a RecordStoreFile for storing the record store.
try {
dbraf = new RecordStoreFile(uidPath);
/*
* At this point we've opened the RecordStoreFile. If we
* created a new record store, initialize the db attributes.
*/
if (create && !exists) {
// Initialize record store attributes
dbraf.seek(RS_SIGNATURE);
// Update the timestamp
RecordStore.putLong(System.currentTimeMillis(),
DB_INIT, RS_LAST_MODIFIED);
RecordStore.putInt(48, DB_INIT, RS_DATA_START);
RecordStore.putInt(48, DB_INIT, RS_DATA_END);
dbraf.write(DB_INIT);
} else {
/*
* Create a buffer and read the database attributes
* Read the record store attributes. Set up internal state.
*/
byte[] buf = new byte[DB_INIT.length];
dbraf.seek(RS_SIGNATURE);
dbraf.read(buf);
/*
* Verify that the file is actually a record store
* by verifying the record store "signature."
*/
for (int i = 0; i < SIGNATURE_LENGTH; i++) {
if (buf[i] != DB_INIT[i]) {
throw new RecordStoreException("invalid record "+
"store contents");
}
}
// Convert byte array to internal state variables.
dbNumLiveRecords = RecordStore.getInt(buf, RS_NUM_LIVE);
dbVersion = RecordStore.getInt(buf, RS_VERSION);
dbAuthMode = RecordStore.getInt(buf, RS_AUTHMODE);
dbNextRecordID = RecordStore.getInt(buf, RS_NEXT_ID);
dbFirstRecordOffset = RecordStore.getInt(buf, RS_REC_START);
dbFirstFreeBlockOffset = RecordStore.getInt(buf,
RS_FREE_START);
dbLastModified = RecordStore.getLong(buf, RS_LAST_MODIFIED);
dbDataStart = RecordStore.getInt(buf, RS_DATA_START);
dbDataEnd = RecordStore.getInt(buf, RS_DATA_END);
}
} catch (java.io.IOException ioe) {
try {
if (dbraf != null) {
dbraf.close();
}
} catch (java.io.IOException ioe2) {
// ignore exception within exception block
} finally {
dbraf = null;
}
throw new RecordStoreException("error opening record store " +
"file");
}
|
Methods Summary |
---|
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) {
checkOpen();
if (!checkWritable()) {
throw new SecurityException();
}
if ((data == null) && (numBytes > 0)) {
throw new NullPointerException("illegal arguments: null " +
"data, numBytes > 0");
}
// get recordId for new record, update db's dbNextRecordID
int id = dbNextRecordID++;
/*
* Find the offset where this record should be stored and
* seek to that location in the file. allocateNewRecordStorage()
* allocates the space for this record.
*/
RecordHeader rh = allocateNewRecordStorage(id, numBytes);
try {
if (data != null) {
rh.write(data, offset);
}
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing new record "
+ "data");
}
// Update the state changes to the db file.
dbNumLiveRecords++;
dbVersion++;
storeDBState();
// tell listeners a record has been added
notifyRecordAddedListeners(id);
// Return the new record id
return id;
}
| public void | addRecordListener(RecordListener listener)Adds the specified RecordListener. If the specified listener
is already registered, it will not be added a second time.
When a record store is closed, all listeners are removed.
synchronized (rsLock) {
if (!recordListener.contains(listener)) {
recordListener.addElement(listener);
}
}
| private javax.microedition.rms.RecordStore$RecordHeader | allocateNewRecordStorage(int id, int dataSize)Returns a new record header for record id large
enough to hold dataSize bytes of record data.
Picks a free block using a first fit strategy that is
large enough for a record header and the associated
record data. The block will be a multiple of DB_BLOCK_SIZE.
int allocSize = getAllocSize(dataSize);
boolean foundBlock = false;
/*
* Traverse the free block linked list in the file, looking
* for the first fit
*/
RecordHeader block = new RecordHeader();
try {
int offset = dbFirstFreeBlockOffset;
while (offset != 0) {
block.load(offset);
// If block is big enough, use it.
if (block.blockSize >= allocSize) {
foundBlock = true;
break; // use this free block
}
offset = block.dataLenOrNextFree; // next free block
}
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error finding first fit block");
}
if (foundBlock == false) {
/*
* No free block was found that would hold this record, so
* find the last (biggest offset) record in the file and
* append this record after it.
*/
// Is there room to grow the file?
if (RecordStoreFile.spaceAvailable() < allocSize) {
throw new RecordStoreFullException();
}
block = new RecordHeader(dbDataEnd, id,
dbFirstRecordOffset,
allocSize, dataSize);
try {
block.store();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing "+
"new record data");
}
dbFirstRecordOffset = dbDataEnd;
dbDataEnd += allocSize;
} else {
// block is where the new record should be stored
if (block.id != -1) {
throw new RecordStoreException("ALLOC ERR " + block.id +
" is not a free block!");
}
removeFreeBlock(block); // remove from free block list
block.id = id;
if (block.blockSize - allocSize >=
DB_BLOCK_SIZE + DB_RECORD_HEADER_LENGTH) {
splitRecord(block, allocSize); // sets block.blockSize
}
block.dataLenOrNextFree = dataSize;
try {
block.store();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing free block "+
"after alloc");
}
}
// add new record to cache
recHeadCache.insert(block);
return block;
| private void | checkOpen()Throws a RecordStoreNotOpenException if the RecordStore
is closed. (A RecordStore is closed if the RecordStoreFile
instance variable dbraf is null.
if (dbraf == null) {
throw new RecordStoreNotOpenException();
}
| private boolean | checkOwner()Internal method to check record store owner vs. the vendor and suite
of the currently running midlet
// varies with currently running midlet suite
String myUid = RecordStoreFile.getUniqueIdPath(recordStoreName);
// fixed at the time dbraf is created
String rsfUid = dbraf.getUniqueIdPath();
if (myUid.equals(rsfUid)) {
return true;
} else {
return false;
}
| private boolean | checkWritable()Internal method to determine if writing to this record store
is allowed for the calling MIDlet. Returns true
if checkOwner() returns true or
dbAuthMode == 1 when checkOwner()
returns false .
if (checkOwner()) {
return true;
} else {
if (dbAuthMode == AUTHMODE_ANY) { // Read-Write mode
return true;
}
}
return false;
| 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) {
synchronized (dbCacheLock) {
checkOpen();
/*
* Find the record store within the record store cache.
* A linear seagrch is OK assuming there won't be many
* concurrently open record stores.
*/
RecordStore db = null;
for (int n = 0; n < dbCache.size(); n++) {
db = (RecordStore)dbCache.elementAt(n);
if (db == this) {
db.opencount--;
break;
}
}
if (db.opencount <= 0) { // free stuff - final close
dbCache.removeElement(db);
try {
// closing now...no need to listen
if (!recordListener.isEmpty()) {
recordListener.removeAllElements();
}
// close native fd
if (dbFirstFreeBlockOffset != 0) {
compactRecords(); // compact before close
// truncate file to compacted size
dbraf.truncate(dbDataEnd);
}
dbraf.close();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error closing .db " +
"file");
} finally {
dbraf = null;
recHeadCache = null;
}
}
}
}
| private void | compactRecords()Remove free blocks from the record store and compact records
with data into as small a space in rsFile as
possible. Operates from smallest to greatest offset in
rsFile , 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 rsFile size.
int offset = dbDataStart; // after record store header structure
int target = 0;
int bytesLeft;
int numToMove;
byte[] chunkBuffer = new byte[DB_COMPACTBUFFER_SIZE];
RecordHeader rh = new RecordHeader();
int prevRec = 0;
while (offset < dbDataEnd) {
try {
rh.load(offset);
} catch (java.io.IOException ioe) {
// NOTE - should throw some exception here
System.out.println("Unexpected IOException in CompactRS!");
}
if (rh.id == -1) { // a free block
if (target == 0) {
target = offset;
} // else skip free block
offset += rh.blockSize;
} else { // a record block
if (target == 0) {
// No move needed so far.
prevRec = offset;
offset += rh.blockSize;
} else {
int old_offset = target;
// Move a record back in the file
rh.offset = target;
rh.nextOffset = prevRec;
try {
rh.store();
offset += DB_RECORD_HEADER_LENGTH;
target += DB_RECORD_HEADER_LENGTH;
bytesLeft = (rh.blockSize - DB_RECORD_HEADER_LENGTH);
while (bytesLeft > 0) {
if (bytesLeft < DB_COMPACTBUFFER_SIZE) {
numToMove = bytesLeft;
} else {
numToMove = DB_COMPACTBUFFER_SIZE;
}
dbraf.seek(offset);
dbraf.read(chunkBuffer, 0, numToMove);
dbraf.seek(target);
dbraf.write(chunkBuffer, 0, numToMove);
offset += numToMove;
target += numToMove;
bytesLeft -= numToMove;
}
} catch (java.io.IOException ioe) {
// NOTE - should throw some exception here
System.out.println("Unexpected IOException " +
"in CompactRS!");
}
prevRec = old_offset;
}
}
}
if (rh.offset != 0) {
dbDataEnd = rh.offset + rh.blockSize;
}
dbFirstRecordOffset = rh.offset;
dbFirstFreeBlockOffset = 0;
storeDBState();
| public void | deleteRecord(int recordId)The record is deleted from the record store. The recordId for
this record is NOT reused.
synchronized (rsLock) {
checkOpen();
if (!checkWritable()) {
throw new SecurityException();
}
RecordHeader rh = null; // record header
try {
rh = findRecord(recordId, false);
freeRecord(rh); // calls rh.store
recHeadCache.invalidate(rh.id);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error updating file after" +
" record deletion");
}
// update database header info and sync to file
dbNumLiveRecords--;
dbVersion++;
storeDBState();
// tell listeners a record has been deleted
notifyRecordDeletedListeners(recordId);
}
| public static void | deleteRecordStore(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.
String uidPath = RecordStoreFile.getUniqueIdPath(recordStoreName);
// Check the record store cache for a db with the same name
synchronized (dbCacheLock) {
RecordStore db;
for (int n = 0; n < dbCache.size(); n++) {
db = (RecordStore)dbCache.elementAt(n);
if (db.uniqueIdPath.equals(uidPath)) {
// cannot delete an open record store
throw new RecordStoreException("deleteRecordStore error:"
+ " record store is"
+ " still open");
}
}
// this record store is not currently open
if (RecordStoreFile.exists(uidPath)) {
boolean success = RecordStoreFile.deleteFile(uidPath);
if (!success) {
throw new RecordStoreException("deleteRecordStore " +
"failed");
}
} else {
throw new RecordStoreNotFoundException("deleteRecordStore " +
"error: file " +
"not found");
}
}
| public RecordEnumeration | enumerateRecords(RecordFilter filter, RecordComparator comparator, boolean keepUpdated)Returns an enumeration for traversing a set of records in the
record store in an optionally specified order.
The filter, if non-null, will be used to determine what
subset of the record store records will be used.
The comparator, if non-null, will be used to determine the
order in which the records are returned.
If both the filter and comparator is null, the enumeration
will traverse all records in the record store in an undefined
order. This is the most efficient way to traverse all of the
records in a record store. If a filter is used with a null
comparator, the enumeration will traverse the filtered records
in an undefined order.
The first call to RecordEnumeration.nextRecord()
returns the record data from the first record in the sequence.
Subsequent calls to RecordEnumeration.nextRecord()
return the next consecutive record's data. To return the record
data from the previous consecutive from any
given point in the enumeration, call previousRecord() .
On the other hand, if after creation the first call is to
previousRecord() , the record data of the last element
of the enumeration will be returned. Each subsequent call to
previousRecord() will step backwards through the
sequence.
checkOpen();
return new RecordEnumerationImpl(this, filter,
comparator, keepUpdated);
| private javax.microedition.rms.RecordStore$RecordHeader | findRecord(int recordId, boolean addToCache)Find the record header for a record recordId .
RecordHeader rh;
int offset;
int cur_offset = dbFirstRecordOffset;
// if no records exist, throw an exception
if (cur_offset == 0) {
throw new InvalidRecordIDException();
}
// look for the record in the cache
rh = recHeadCache.get(recordId);
if (rh != null) {
return rh;
}
/*
* requested record header is NOT in cache...
* search through the linked list of records
* in the file.
*/
rh = new RecordHeader();
while (cur_offset != 0) {
rh.load(cur_offset);
if (rh.id == recordId) {
break;
} else {
cur_offset = rh.nextOffset;
}
}
if (cur_offset == 0) {
// hit the end of the linked list w/o finding record.
throw new InvalidRecordIDException();
}
if (addToCache) {
recHeadCache.insert(rh);
}
return rh;
| private void | freeRecord(javax.microedition.rms.RecordStore$RecordHeader rh)Free a record into the Free list.
Turns the RecordHeader block rh into a free
block, then adds it to the free block linked list.
After calling this method the caller must call
storeDBState .
if (rh.offset == dbFirstRecordOffset) {
// don't put free blocks at the end of the record file
dbFirstRecordOffset = rh.nextOffset;
dbDataEnd = rh.offset;
} else {
rh.id = -1; // indicate this is a free block
rh.dataLenOrNextFree = dbFirstFreeBlockOffset;
// insert this new free block at front of free list
dbFirstFreeBlockOffset = rh.offset;
try {
rh.store();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("free record failed");
}
}
| private int | getAllocSize(int numBytes)Return the block allocation size for a record with numBytes
data bytes. This includes space for the record header
and is a multiple of DB_BLOCK_SIZE .
int rv;
int pad;
rv = DB_RECORD_HEADER_LENGTH + numBytes;
pad = DB_BLOCK_SIZE - (rv % DB_BLOCK_SIZE);
if (pad != DB_BLOCK_SIZE) {
rv += pad;
}
return rv;
| static int | getInt(byte[] data, int offset)A convenience method for converting a byte array into
an int (assumes big-endian byte ordering).
int r = data[offset++];
r = (r << 8) | ((int)(data[offset++]) & 0xff);
r = (r << 8) | ((int)(data[offset++]) & 0xff);
r = (r << 8) | ((int)(data[offset++]) & 0xff);
return r;
| public long | getLastModified()Returns the last time the record store was modified, in the
format used by System.currentTimeMillis().
checkOpen();
return dbLastModified;
| static long | getLong(byte[] data, int offset)A convenience method for converting a byte array into
a long (assumes big-endian byte ordering).
long r = data[offset++];
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
r = (r << 8) | ((long)(data[offset++]) & 0xff);
return r;
| public java.lang.String | getName()Returns the name of this RecordStore.
checkOpen();
return recordStoreName;
| 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() .
checkOpen();
return dbNextRecordID;
| public int | getNumRecords()Returns the number of records currently in the record store.
checkOpen();
return dbNumLiveRecords;
| public int | getRecord(int recordId, byte[] buffer, int offset)Returns the data stored in the given record.
synchronized (rsLock) {
checkOpen();
RecordHeader rh;
try {
// throws InvalidRecordIDException
rh = findRecord(recordId, true);
rh.read(buffer, offset);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
return rh.dataLenOrNextFree;
}
| public byte[] | getRecord(int recordId)Returns a copy of the data stored in the given record.
synchronized (rsLock) {
checkOpen();
int size = 0;
byte[] data = null;
try {
// throws InvalidRecordIDException
RecordHeader rh = findRecord(recordId, true);
if (rh.dataLenOrNextFree == 0) {
return null;
}
data = new byte[rh.dataLenOrNextFree];
rh.read(data, 0);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
return data;
}
| int[] | getRecordIDs()Returns all of the recordId's currently in the record store.
MUST be called after obtaining rsLock, e.g in a
synchronized (rsLock) { block.
if (dbraf == null) { // lower overhead than checkOpen()
return null;
}
int index = 0;
int[] tmp = new int[dbNumLiveRecords];
int offset = dbFirstRecordOffset; // start at beginning of file
RecordHeader rh = new RecordHeader();
try {
while (offset != 0) {
rh.load(offset);
if (rh.id > 0) {
tmp[index++] = rh.id;
}
offset = rh.nextOffset;
}
} catch (java.io.IOException ioe) {
return null;
}
return tmp;
| public int | getRecordSize(int recordId)Returns the size (in bytes) of the MIDlet data available
in the given record.
synchronized (rsLock) {
checkOpen();
try {
// throws InvalidRecordIDException
RecordHeader rh = findRecord(recordId, true);
return (rh.dataLenOrNextFree);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error reading record data");
}
}
| 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.
checkOpen();
// return the file size of the record store file
return dbDataEnd;
| 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.
checkOpen();
int rv = RecordStoreFile.spaceAvailable() -
DB_BLOCK_SIZE - DB_RECORD_HEADER_LENGTH;
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.
checkOpen();
return dbVersion;
| boolean | isOpen()Get the open status of this record store. (Package accessable
for use by record enumeration objects.)
if (dbraf == null) {
return false;
}
return true;
| public static java.lang.String[] | listRecordStores()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.
// static calls synchronize on dbCacheLock
synchronized (dbCacheLock) {
String[] returnstrings = RecordStoreFile.listRecordStores();
return returnstrings;
}
| private void | notifyRecordAddedListeners(int recordId)Notifies all registered listeners that a record was added.
for (int i = 0; i < recordListener.size(); i++) {
RecordListener rl = (RecordListener)recordListener.elementAt(i);
rl.recordAdded(this, recordId);
}
| private void | notifyRecordChangedListeners(int recordId)Notifies all registered listeners that a record changed.
for (int i = 0; i < recordListener.size(); i++) {
RecordListener rl = (RecordListener)recordListener.elementAt(i);
rl.recordChanged(this, recordId);
}
| private void | notifyRecordDeletedListeners(int recordId)Notifies all registered listeners that a record was deleted.
for (int i = 0; i < recordListener.size(); i++) {
RecordListener rl = (RecordListener)recordListener.elementAt(i);
rl.recordDeleted(this, recordId);
}
| public static javax.microedition.rms.RecordStore | openRecordStore(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 RecordStore object.
String uidPath = RecordStoreFile.getUniqueIdPath(recordStoreName);
synchronized (dbCacheLock) {
if (recordStoreName.length() > 32 ||
recordStoreName.length() == 0) {
throw new IllegalArgumentException();
}
// Cache record store objects and ensure that there is only
// one record store object in memory for any given record
// store file. This is good for memory use. This is NOT safe
// in the situation where multiple VM's may be executing code
// concurrently. In that case, you have to sync things through
// file locking or something similar.
// Check the record store cache for a db with the same name
RecordStore db;
for (int n = 0; n < dbCache.size(); n++) {
db = (RecordStore)dbCache.elementAt(n);
if (db.uniqueIdPath.equals(uidPath)) {
db.opencount++; // times rs has been opened
return db; // return ref to cached record store
}
}
/*
* Record store not found in cache, so create it.
*/
db = new RecordStore(uidPath, recordStoreName, createIfNecessary);
/*
* Now add the new record store to the cache
*/
db.opencount = 1;
dbCache.addElement(db);
return db;
}
| public static javax.microedition.rms.RecordStore | openRecordStore(java.lang.String recordStoreName, boolean createIfNecessary, int authmode, boolean writable)Open (and possibly create) a record store that can be shared
with other MIDlet suites. The RecordStore is owned by the
current MIDlet suite. The authorization mode is set when the
record store is created, as follows:
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.
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 RecordStore object.
RecordStore rs = RecordStore.openRecordStore(recordStoreName,
createIfNecessary);
rs.setMode(authmode, writable);
return rs;
| public static javax.microedition.rms.RecordStore | openRecordStore(java.lang.String recordStoreName, java.lang.String vendorName, java.lang.String suiteName)Open a record store associated with the named MIDlet suite.
The MIDlet suite is identified by MIDlet vendor and MIDlet
name. Access is granted only if the authorization mode of the
RecordStore allows access by the current MIDlet suite. Access
is limited by the authorization mode set when the record store
was created:
AUTHMODE_PRIVATE - Succeeds only if vendorName
and suiteName identify the current MIDlet suite; this
case behaves identically to
openRecordStore(recordStoreName,
createIfNecessary) .
AUTHMODE_ANY - Always succeeds.
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.
Untrusted MIDlet suites are allowed to share data but
this is not recommended. The authenticity of the
origin of untrusted MIDlet suites cannot be verified
so shared data may be used unscrupulously.
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 RecordStore object.
If a MIDlet calls this method to open a record store from
its own suite, the behavior is identical to calling:
{@link #openRecordStore(String, boolean)
openRecordStore(recordStoreName, false)}
if (vendorName == null || suiteName == null) {
throw new IllegalArgumentException("vendorName and " +
"suiteName must be " +
"non null");
}
synchronized (dbCacheLock) {
if (recordStoreName.length() > 32 ||
recordStoreName.length() == 0) {
throw new IllegalArgumentException();
}
// Cache record store objects and ensure that there is only
// one record store object in memory for any given record
// store file. This is good for memory use. This is NOT safe
// in the situation where multiple VM's may be executing code
// concurrently. In that case, you have to sync things through
// file locking or something similar.
// Check the record store cache for a db with the same name
RecordStore db;
String uidPath = RecordStoreFile.getUniqueIdPath(vendorName,
suiteName,
recordStoreName);
for (int n = 0; n < dbCache.size(); n++) {
db = (RecordStore)dbCache.elementAt(n);
if (db.uniqueIdPath.equals(uidPath)) {
if (db.checkOwner() == false &&
db.dbAuthMode == AUTHMODE_PRIVATE) {
throw new SecurityException();
} else {
db.opencount++; // times rs has been opened
return db; // return ref to cached record store
}
}
}
/*
* Record store not found in cache, so create it.
*/
db = new RecordStore(uidPath, recordStoreName, false);
/*
* Now add the new record store to the cache
*/
db.opencount = 1;
dbCache.addElement(db);
if (db.checkOwner() == false &&
db.dbAuthMode == AUTHMODE_PRIVATE) {
db.closeRecordStore();
throw new SecurityException();
} else {
return db;
}
}
| static int | putInt(int i, byte[] data, int offset)A convenience method for converting an integer into
a byte array.
data[offset++] = (byte)((i >> 24) & 0xff);
data[offset++] = (byte)((i >> 16) & 0xff);
data[offset++] = (byte)((i >> 8) & 0xff);
data[offset] = (byte)(i & 0xff);
return 4;
| static int | putLong(long l, byte[] data, int offset)A convenience method for converting a long into
a byte array.
data[offset++] = (byte)((l >> 56) & 0xff);
data[offset++] = (byte)((l >> 48) & 0xff);
data[offset++] = (byte)((l >> 40) & 0xff);
data[offset++] = (byte)((l >> 32) & 0xff);
data[offset++] = (byte)((l >> 24) & 0xff);
data[offset++] = (byte)((l >> 16) & 0xff);
data[offset++] = (byte)((l >> 8) & 0xff);
data[offset] = (byte)(l & 0xff);
return 8;
| private void | removeFreeBlock(javax.microedition.rms.RecordStore$RecordHeader blockToFree)Remove a free block from the free block linked list
RecordHeader block = new RecordHeader();
RecordHeader prev = new RecordHeader();
RecordHeader tmp = null;
try {
int offset = dbFirstFreeBlockOffset;
while (offset != 0) {
block.load(offset);
if (block.offset == blockToFree.offset) {
if (block.id != -1) {
throw new RecordStoreException("removeFreeBlock id" +
" is not -1");
}
if (prev.offset == 0) {
// Set next free block as new freelist head
dbFirstFreeBlockOffset = block.dataLenOrNextFree;
} else {
/*
* Update previous block's pointer to the
* block this block was pointing to
*/
prev.dataLenOrNextFree = block.dataLenOrNextFree;
prev.store();
}
}
offset = block.dataLenOrNextFree;
// avoid creating lots of garbage!
tmp = prev;
prev = block;
block = tmp;
}
} catch (java.io.IOException ioe) {
throw new RecordStoreException("removeFreeBlock block not found");
}
| public void | removeRecordListener(RecordListener listener)Removes the specified RecordListener. If the specified listener
is not registered, this method does nothing.
synchronized (rsLock) {
recordListener.removeElement(listener);
}
| 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) {
if (checkOwner() == false) {
throw new SecurityException();
} else if (authmode != AUTHMODE_PRIVATE &&
authmode != AUTHMODE_ANY) {
throw new IllegalArgumentException();
} else {
dbAuthMode = authmode;
if ((authmode == AUTHMODE_ANY) && (writable == false)) {
dbAuthMode = AUTHMODE_ANY_RO;
}
}
// flush record store header here
storeDBState();
}
| 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) {
checkOpen();
if (!checkWritable()) {
throw new SecurityException();
}
if ((newData == null) && (numBytes > 0)) {
throw new NullPointerException();
}
RecordHeader rh = null;
RecordHeader newrh = null;
try {
rh = findRecord(recordId, false); // throws InvalidRIDException
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error finding record data");
}
/*
* The size of the data and space allocated to the
* current record is known here, as is the new size.
* Determine if the new data will fit, or if this
* record will have to be stored elsewhere.
*/
if (numBytes <= rh.blockSize - DB_RECORD_HEADER_LENGTH) {
/*
* The new data should fit within the existing record
* location in the file. Store the new data and
* patch up the record's header fields.
*/
int allocSize = getAllocSize(numBytes);
if (rh.blockSize - allocSize >=
DB_BLOCK_SIZE + DB_RECORD_HEADER_LENGTH) {
splitRecord(rh, allocSize); // sets rh.blockSize
}
rh.dataLenOrNextFree = numBytes;
try {
rh.store(); // write the new record header
recHeadCache.insert(rh); // add to cache
if (newData != null) {
rh.write(newData, offset);
}
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing record" +
" data");
}
} else {
/*
* The new data is longer than the old data. It needs to
* be relocated to elsewhere within <code>dbfile</code>.
* Search the free list to see if there's a space where to
* store it. Otherwise append it to the end of the file.
*/
freeRecord(rh); // calls rh.store()
newrh = allocateNewRecordStorage(recordId, numBytes);
try {
if (newData != null) {
newrh.write(newData, offset);
// NOTE: I/O exception leaves space allocated & unused
}
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error moving record " +
"data");
}
}
// update database header info and sync to file
dbVersion++;
storeDBState();
notifyRecordChangedListeners(recordId);
}
| private void | splitRecord(javax.microedition.rms.RecordStore$RecordHeader recHead, int allocSize)Splits a free block off the tail end of a large
record block which contains extra free space.
After calling this method, the caller must call
storeDBState .
On return, recHead.blockSize will contain
allocSize.
RecordHeader newfb;
int extraSpace = recHead.blockSize - allocSize;
int oldBlockSize = recHead.blockSize;
recHead.blockSize = allocSize;
// only split records inside the linked list
if (recHead.offset != dbFirstRecordOffset) {
int fboffset = recHead.offset + allocSize;
newfb = new RecordHeader(fboffset, -1, recHead.offset,
extraSpace, 0);
try {
freeRecord(newfb); // write new free block to disk
RecordHeader prh = new RecordHeader(recHead.offset +
oldBlockSize);
prh.nextOffset = fboffset;
prh.store();
recHeadCache.invalidate(prh.id);
storeDBState();
} catch (java.io.IOException ioe) {
throw new RecordStoreException("splitRecord error");
}
} else {
// drop free space at the end of the file
dbDataEnd = recHead.offset + recHead.blockSize;
}
| private void | storeDBState()Helper method that stores the internal state variables
into the record store file.
checkopen should have been called. will not work
if dbraf is not open
Updates dbLastModified time to current system time
try {
// set modification time
dbLastModified = System.currentTimeMillis();
// Capture the db state into the byte array
RecordStore.putInt(dbNumLiveRecords, dbState, RS_NUM_LIVE);
RecordStore.putInt(dbAuthMode, dbState, RS_AUTHMODE);
RecordStore.putInt(dbVersion, dbState, RS_VERSION);
RecordStore.putInt(dbNextRecordID, dbState, RS_NEXT_ID);
RecordStore.putInt(dbFirstRecordOffset, dbState, RS_REC_START);
RecordStore.putInt(dbFirstFreeBlockOffset, dbState, RS_FREE_START);
RecordStore.putLong(dbLastModified, dbState, RS_LAST_MODIFIED);
RecordStore.putInt(dbDataStart, dbState, RS_DATA_START);
RecordStore.putInt(dbDataEnd, dbState, RS_DATA_END);
// Write the state to the db file
dbraf.seek(SIGNATURE_LENGTH); // skip RS header 8 bytes
int numbytes = DB_INIT.length - SIGNATURE_LENGTH;
dbraf.write(dbState, SIGNATURE_LENGTH, numbytes);
} catch (java.io.IOException ioe) {
throw new RecordStoreException("error writing record store " +
"attributes");
}
|
|