FileDocCategorySizeDatePackage
RecordStore.javaAPI DocJ2ME MIDP 2.078052Thu Nov 07 12:02:28 GMT 2002javax.microedition.rms

RecordStore

public 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.

since
MIDP 1.0

Fields Summary
public static final int
AUTHMODE_PRIVATE
Authorization to allow access only to the current MIDlet suite. AUTHMODE_PRIVATE has a value of 0.
public static final int
AUTHMODE_ANY
Authorization to allow access to any MIDlet suites. AUTHMODE_ANY has a value of 1.
private static final int
AUTHMODE_ANY_RO
Internal indicator for AUTHMODE_ANY with read only access AUTHMODE_ANY_RO has a value of 2.
private static final byte[]
DB_INIT
pre initialized RecordStore header structure
private static final int
SIGNATURE_LENGTH
length of the signature string in bytes
private static final int
DB_RECORD_HEADER_LENGTH
size of a per record meta-data object
private static final int
DB_BLOCK_SIZE
storage 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_SIZE
size of the buffer for compacting record store
private static Vector
dbCache
cache of open RecordStore instances
private static final Object
dbCacheLock
lock to protect static dbcache state
private String
recordStoreName
name of this record store
private String
uniqueIdPath
unique storage id for this record store
private int
opencount
number of open instances of this record store
private com.sun.midp.rms.RecordStoreFile
dbraf
RecordStoreFile where this record store is stored
Object
rsLock
lock used to synchronize this record store
private Vector
recordListener
recordListeners of this record store
private RecordHeaderCache
recHeadCache
cache of record headers
private static int
CACHE_SIZE
number of direct mapped cache entries
private static byte[]
recHeadBuf
static buffer used in loading/storing RecordHeader data
private int
dbNextRecordID
next record's id
private int
dbVersion
record store version
private int
dbAuthMode
authorization mode of this record store 0: AUTHMODE_PRIVATE, Read/Write 1: AUTHMODE_ANY, Read/Write 2: AUTHMODE_ANY_RO, Read-Only
private int
dbNumLiveRecords
count of live records
private long
dbLastModified
time record store was last modified (in milliseconds
private int
dbFirstRecordOffset
offset of first record
private int
dbFirstFreeBlockOffset
offset of first free block
private int
dbDataStart
offset of the first data block
private int
dbDataEnd
offset of the last data block
private static byte[]
dbState
static buffer used in loading/storing dbState
private static final int
RS_SIGNATURE
RS_SIGNATURE offset
private static final int
RS_NUM_LIVE
RS_NUM_LIVE offset
private static final int
RS_AUTHMODE
RS_AUTHMODE offset
private static final int
RS_VERSION
RS_VERSION offset
private static final int
RS_NEXT_ID
RS_NEXT_ID offset
private static final int
RS_REC_START
RS_REC_START offset
private static final int
RS_FREE_START
RS_FREE_START offset
private static final int
RS_LAST_MODIFIED
RS_LAST_MODIFIED offset
private static final int
RS_DATA_START
RS_START_OF_DATA offset
private static final int
RS_DATA_END
RS_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.

param
uidPath unique storage id for this record store
param
recordStoreName a string to name the record store
param
create if true, create the record store if it doesn't exist
exception
RecordStoreException if something goes wrong setting up the new RecordStore.
exception
RecordStoreNotFoundException if can't find the record store and create is set to false.
exception
RecordStoreFullException if there is no room in storage to create a new record store

	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 intaddRecord(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.

param
data the data to be stored in this record. If the record is to have zero-length data (no data), this parameter may be null.
param
offset the index into the data buffer of the first relevant byte for this record
param
numBytes the number of bytes of the data buffer to use for this record (may be zero)
return
the recordId for the new record
exception
RecordStoreNotOpenException if the record store is not open
exception
RecordStoreException if a different record store-related exception occurred
exception
RecordStoreFullException if the operation cannot be completed because the record store has no more room
exception
SecurityException if the MIDlet has read-only access to the RecordStore

	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 voidaddRecordListener(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.

param
listener the RecordChangedListener
see
#removeRecordListener

	synchronized (rsLock) {
	    if (!recordListener.contains(listener)) {
		recordListener.addElement(listener);
	    }
	}
    
private javax.microedition.rms.RecordStore$RecordHeaderallocateNewRecordStorage(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.

param
id the record id to assign to the returned record header.
param
dataSize length of record data that will be stored with this record.
return
a new record header the record store's backing file.

	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 voidcheckOpen()
Throws a RecordStoreNotOpenException if the RecordStore is closed. (A RecordStore is closed if the RecordStoreFile instance variable dbraf is null.

exception
RecordStoreNotOpenException if RecordStore is closed

	if (dbraf == null) {
	    throw new RecordStoreNotOpenException();
	}
    
private booleancheckOwner()
Internal method to check record store owner vs. the vendor and suite of the currently running midlet

return
true if vendor and suite name both match, false otherwise


	// 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 booleancheckWritable()
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.

return
true if the record modification request should be allowed, false otherwise

	if (checkOwner()) {
	    return true;
	} else {
	    if (dbAuthMode == AUTHMODE_ANY) { // Read-Write mode
		return true;
	    }
	}
	return false;
    
public voidcloseRecordStore()
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.

exception
RecordStoreNotOpenException if the record store is not open
exception
RecordStoreException if a different record store-related exception occurred

	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 voidcompactRecords()
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.

exception
RecordStoreNotOpenException if this record store is closed
exception
RecordStoreException if an error occurs during record store compaction

	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 voiddeleteRecord(int recordId)
The record is deleted from the record store. The recordId for this record is NOT reused.

param
recordId the ID of the record to delete
exception
RecordStoreNotOpenException if the record store is not open
exception
InvalidRecordIDException if the recordId is invalid
exception
RecordStoreException if a general record store exception occurs
exception
SecurityException if the MIDlet has read-only access to the RecordStore

	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 voiddeleteRecordStore(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.

param
recordStoreName the MIDlet suite unique record store to delete
exception
RecordStoreException if a record store-related exception occurred
exception
RecordStoreNotFoundException if the record store could not be found

	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 RecordEnumerationenumerateRecords(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.

param
filter if non-null, will be used to determine what subset of the record store records will be used
param
comparator if non-null, will be used to determine the order in which the records are returned
param
keepUpdated if true, the enumerator will keep its enumeration current with any changes in the records of the record store. Use with caution as there are possible performance consequences. If false the enumeration will not be kept current and may return recordIds for records that have been deleted or miss records that are added later. It may also return records out of order that have been modified after the enumeration was built. Note that any changes to records in the record store are accurately reflected when the record is later retrieved, either directly or through the enumeration. The thing that is risked by setting this parameter false is the filtering and sorting order of the enumeration when records are modified, added, or deleted.
exception
RecordStoreNotOpenException if the record store is not open
see
RecordEnumeration#rebuild
return
an enumeration for traversing a set of records in the record store in an optionally specified order

	checkOpen();
	return new RecordEnumerationImpl(this, filter, 
					 comparator, keepUpdated);
    
private javax.microedition.rms.RecordStore$RecordHeaderfindRecord(int recordId, boolean addToCache)
Find the record header for a record recordId.

param
recordId the id of the desired record header.
param
addToCache true if this record should be added to cache if found.
return
the record header for the given record, or null if the record cannot be found.

	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 voidfreeRecord(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.

param
rh RecordHeader of record to make into a free block
exception
RecordStoreException if there is an IO error updating the free list

	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 intgetAllocSize(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.

param
numBytes number of data bytes that will be stored in record
return
the amount of space to allocate for this record.

	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 intgetInt(byte[] data, int offset)
A convenience method for converting a byte array into an int (assumes big-endian byte ordering).

param
data the byte array returned from the database.
param
offset the offset into the array of the first byte to start from.
return
an int corresponding to the first four bytes of the array passed in.

	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 longgetLastModified()
Returns the last time the record store was modified, in the format used by System.currentTimeMillis().

return
the last time the record store was modified, in the format used by System.currentTimeMillis()
exception
RecordStoreNotOpenException if the record store is not open

	checkOpen();

	return dbLastModified;
    
static longgetLong(byte[] data, int offset)
A convenience method for converting a byte array into a long (assumes big-endian byte ordering).

param
data the byte array returned from the database.
param
offset the offset into the array of the first byte to start from.
return
a long corresponding to the first eight bytes of the array passed in.

	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.StringgetName()
Returns the name of this RecordStore.

return
the name of this RecordStore
exception
RecordStoreNotOpenException if the record store is not open

	checkOpen();
	return recordStoreName;
    
public intgetNextRecordID()
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
the recordId of the next record to be added to the record store
exception
RecordStoreNotOpenException if the record store is not open
exception
RecordStoreException if a different record store-related exception occurred

	checkOpen();
	return dbNextRecordID;
    
public intgetNumRecords()
Returns the number of records currently in the record store.

return
the number of records currently in the record store
exception
RecordStoreNotOpenException if the record store is not open

	checkOpen();
	return dbNumLiveRecords;
    
public intgetRecord(int recordId, byte[] buffer, int offset)
Returns the data stored in the given record.

param
recordId the ID of the record to use in this operation
param
buffer the byte array in which to copy the data
param
offset the index into the buffer in which to start copying
exception
RecordStoreNotOpenException if the record store is not open
exception
InvalidRecordIDException if the recordId is invalid
exception
RecordStoreException if a general record store exception occurs
exception
ArrayIndexOutOfBoundsException if the record is larger than the buffer supplied
return
the number of bytes copied into the buffer, starting at index offset
see
#setRecord

	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.

param
recordId the ID of the record to use in this operation
exception
RecordStoreNotOpenException if the record store is not open
exception
InvalidRecordIDException if the recordId is invalid
exception
RecordStoreException if a general record store exception occurs
return
the data stored in the given record. Note that if the record has no data, this method will return null.
see
#setRecord

	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.

return
an array of the recordId's currently in the record store or null if the record store is closed.

	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 intgetRecordSize(int recordId)
Returns the size (in bytes) of the MIDlet data available in the given record.

param
recordId the ID of the record to use in this operation
return
the size (in bytes) of the MIDlet data available in the given record
exception
RecordStoreNotOpenException if the record store is not open
exception
InvalidRecordIDException if the recordId is invalid
exception
RecordStoreException if a general record store exception occurs

	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 intgetSize()
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.

exception
RecordStoreNotOpenException if the record store is not open
return
the size of the record store in bytes

	checkOpen();
	// return the file size of the record store file
	return dbDataEnd;
    
public intgetSizeAvailable()
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.

exception
RecordStoreNotOpenException if the record store is not open
return
the amount of additional room (in bytes) available for this record store to grow

	checkOpen();
	int rv = RecordStoreFile.spaceAvailable() - 
	    DB_BLOCK_SIZE - DB_RECORD_HEADER_LENGTH;
	return (rv < 0) ? 0 : rv;
    
public intgetVersion()
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
the current record store version
exception
RecordStoreNotOpenException if the record store is not open

	checkOpen();
	return dbVersion;
    
booleanisOpen()
Get the open status of this record store. (Package accessable for use by record enumeration objects.)

return
true if record store is open, false otherwise.

	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.

return
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.

        // static calls synchronize on dbCacheLock
        synchronized (dbCacheLock) {
	  String[] returnstrings = RecordStoreFile.listRecordStores();
	  return returnstrings;
	}
    
private voidnotifyRecordAddedListeners(int recordId)
Notifies all registered listeners that a record was added.

param
recordId the record id of the added record.

	for (int i = 0; i < recordListener.size(); i++) {
	    RecordListener rl = (RecordListener)recordListener.elementAt(i);
	    rl.recordAdded(this, recordId);
	}
    
private voidnotifyRecordChangedListeners(int recordId)
Notifies all registered listeners that a record changed.

param
recordId the record id of the changed record.

	for (int i = 0; i < recordListener.size(); i++) {
	    RecordListener rl = (RecordListener)recordListener.elementAt(i);
	    rl.recordChanged(this, recordId);
	}
    
private voidnotifyRecordDeletedListeners(int recordId)
Notifies all registered listeners that a record was deleted.

param
recordId the record id of the changed record.

	for (int i = 0; i < recordListener.size(); i++) {
	    RecordListener rl = (RecordListener)recordListener.elementAt(i);
	    rl.recordDeleted(this, recordId);
	}
    
public static javax.microedition.rms.RecordStoreopenRecordStore(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.

param
recordStoreName the MIDlet suite unique name for the record store, consisting of between one and 32 Unicode characters inclusive.
param
createIfNecessary if true, the record store will be created if necessary
return
RecordStore object for the record store
exception
RecordStoreException if a record store-related exception occurred
exception
RecordStoreNotFoundException if the record store could not be found
exception
RecordStoreFullException if the operation cannot be completed because the record store is full
exception
IllegalArgumentException if recordStoreName is invalid

	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.RecordStoreopenRecordStore(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.

param
recordStoreName the MIDlet suite unique name for the record store, consisting of between one and 32 Unicode characters inclusive.
param
createIfNecessary if true, the record store will be created if necessary
param
authmode the mode under which to check or create access. Must be one of AUTHMODE_PRIVATE or AUTHMODE_ANY. This argument is ignored if the RecordStore exists.
param
writable true if the RecordStore is to be writable by other MIDlet suites that are granted access. This argument is ignored if the RecordStore exists.
return
RecordStore object for the record store
exception
RecordStoreException if a record store-related exception occurred
exception
RecordStoreNotFoundException if the record store could not be found
exception
RecordStoreFullException if the operation cannot be completed because the record store is full
exception
IllegalArgumentException if authmode or recordStoreName is invalid
since
MIDP 2.0

	RecordStore rs = RecordStore.openRecordStore(recordStoreName, 
						     createIfNecessary);
	rs.setMode(authmode, writable);
	return rs;
    
public static javax.microedition.rms.RecordStoreopenRecordStore(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)}

param
recordStoreName the MIDlet suite unique name for the record store, consisting of between one and 32 Unicode characters inclusive.
param
vendorName the vendor of the owning MIDlet suite
param
suiteName the name of the MIDlet suite
return
RecordStore object for the record store
exception
RecordStoreException if a record store-related exception occurred
exception
RecordStoreNotFoundException if the record store could not be found
exception
SecurityException if this MIDlet Suite is not allowed to open the specified RecordStore.
exception
IllegalArgumentException if recordStoreName is invalid
since
MIDP 2.0

	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 intputInt(int i, byte[] data, int offset)
A convenience method for converting an integer into a byte array.

param
i the integer to turn into a byte array.
param
data a place to store the bytes of i.
param
offset starting point within data to store i.
return
the number of bytes written to the 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 intputLong(long l, byte[] data, int offset)
A convenience method for converting a long into a byte array.

param
l the long to turn into a byte array.
param
data a place to store the bytes of l.
param
offset Starting point within data to store l.
return
the number of bytes written to the 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 voidremoveFreeBlock(javax.microedition.rms.RecordStore$RecordHeader blockToFree)
Remove a free block from the free block linked list

param
blockToFree record header for the free block to remove
exception
recordStoreException if error occurs during the update.

	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 voidremoveRecordListener(RecordListener listener)
Removes the specified RecordListener. If the specified listener is not registered, this method does nothing.

param
listener the RecordChangedListener
see
#addRecordListener

	synchronized (rsLock) {
	    recordListener.removeElement(listener);
	}
    
public voidsetMode(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.

param
authmode the mode under which to check or create access. Must be one of AUTHMODE_PRIVATE or AUTHMODE_ANY.
param
writable true if the RecordStore is to be writable by other MIDlet suites that are granted access
exception
RecordStoreException if a record store-related exception occurred
exception
SecurityException if this MIDlet Suite is not allowed to change the mode of the RecordStore
exception
IllegalArgumentException if authmode is invalid
since
MIDP 2.0


                                                                                                                                                                                                 		                 		            		         		                     
       
			 
     
    
	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 voidsetRecord(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.

param
recordId the ID of the record to use in this operation
param
newData the new data to store in the record
param
offset the index into the data buffer of the first relevant byte for this record
param
numBytes the number of bytes of the data buffer to use for this record
exception
RecordStoreNotOpenException if the record store is not open
exception
InvalidRecordIDException if the recordId is invalid
exception
RecordStoreException if a general record store exception occurs
exception
RecordStoreFullException if the operation cannot be completed because the record store has no more room
exception
SecurityException if the MIDlet has read-only access to the RecordStore
see
#getRecord

	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 voidsplitRecord(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.

param
recHead the current record header
param
allocSize the size that recHEad will have as its blockSize variable when the call returns.
exception
RecordStoreException if there is an error splitting the record

	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 voidstoreDBState()
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");
	}