Each will create a record store, write several records, read back those same records and delete the record store.. We have two methods to write data into a record store: public int addRe
Trang 1private AnimatedTimer midlet; // Main midlet
private Command cmBack;
public OptionsList(String title, int listType, AnimatedTimer midlet) {
// Call list constructor
super(title, listType);
// Save reference to MIDlet so we can
// access the display manager class
this.midlet = midlet;
// Create the list entries
append("Sleep interval", null);
append("Start", null);
append("Stop", null);
// Create command and listen for events
cmBack = new Command("Back", Command.BACK, 1);
// Push current displayable and show the form
// to adjust the timer sleep
Trang 2private AnimatedTimer midlet; // Main midlet
private Command cmBack, // Back to options list
cmHome, // Go to main displayable (canvas) cmSave; // Save new sleep time
private Gauge gaSleep; // Gauge to adjust sleep
public SleepForm(String title, AnimatedTimer midlet)
{
// Call the form constructor
super(title);
// Save reference to MIDlet so we can
// access the display manager class
this.midlet = midlet;
// Commands
cmSave = new Command("Save", Command.SCREEN, 1);
cmBack = new Command("Back", Command.BACK, 2);
cmHome = new Command("Home", Command.SCREEN, 2);
// Gauge to adjust the length of timer sleep
gaSleep = new Gauge("Timer Sleep", true, 100, 1000);
// Set to current sleep Gauge holds values 0 to 100,
// divide the current sleep (milliseconds) by 10
Trang 3// Gauge returns a value between 0 and 100
// We want milliseconds, so multiply by 10
* public void pushDisplayable(Displayable)
* public void popDisplayable()
* public void home()
private Display display; // Reference to Display object
private Displayable mainDisplayable; // Main displayable for MIDlet private Alert alStackError; // Alert for error conditions
public DisplayManager(Display display, Displayable mainDisplayable) {
// Only one display object per midlet, this is it
this.display = display;
this.mainDisplayable = mainDisplayable;
Trang 4// Create an alert displayed when an error occurs
alStackError = new Alert("Displayable Stack Error"); alStackError.setTimeout(Alert.FOREVER); // Modal
}
/* -
* Push the current displayable onto stack and set
* the passed in displayable as active
// On error show an alert
// Once acknowldeged, set 'mainDisplayable' as active display.setCurrent(alStackError, mainDisplayable); }
}
Trang 5Chapter 11 RECORD MANAGEMENT SYSTEM (RMS) Topics in this Chapter
• Persistent Storage Through the Record Store
• Navigating with RecordEnumeration
• Sorting with RecordComparator
• Searching with RecordFilter
• Notification of Changes with RecordListener
• Exception Handling
We can cover a lot of miles with the concepts we've covered upto this point However, we are missing
a significant capability that will eventually catch up with us In fact, it is so ingrained into our daily computing we often take this concept for granted I am referring to storing and retrieving of data Whether the information is as simple as application preferences or as comprehensive as a product catalog, we need a way to manage application-related data
In desktop computing the options are obvious, CD-ROM, local drive, network drive, and so forth It becomes a little more challenging with mobile devices There are size and performance concerns, not
to mention the differences between manufacturers as far as support (or lack of) for file systems and networking
The Record Management System (RMS) is a persistent storage environment within the MIDP This chapter describes how you can read, write, sort and search data within a RMS
Persistent Storage Through the Record Store
As an alternative to using a file system, the RMS uses non-volatile memory to store information This record-oriented database, often referred to as a flat-file, can be thought of as a series of rows in a table, with a unique identifier for each row (see Table 11.1)
Table 11.1 Record Store
Trang 6Naming Record Stores
Each record store name can consist of up to 32 Unicode (16-bit) characters Names are case
sensitive Names must be unique within a MIDlet suite
MIDlets that are packaged within a suite can access not only the record stores they create, but also those of other MIDlets in the suite Figure 11–1 illustrates this concept
Figure 11-1 MIDlet access to record stores The solid lines indicate access to record stores the MIDlet has created Dashed lines represent access to record stores created
by a different MIDlet within the same suite
Trang 7Within "MIDlet Suite One," MIDlet #1 and MIDlet #2 can access all four record stores available as part of the suite The same applies to "Suite Two." However, MIDlets in Suite One cannot access the record stores of Suite Two
Record store names within a suite must be unique That is, Suite One must have all unique record store names However, record store names in one suite may be duplicated in another For example, Suite One and Suite Two both have record stores with the name "ABC" and "DEF."
There are two values maintained by a record store that may be helpful for tracking database usage Each is updated when a record is added, deleted or changed:
1 Version number is an integer value Unfortunately, the starting value when creating a new
record is not defined by the API If you need to track version numbers, you can query the record store immediately after creation using getVersion() to determine the starting value
2 Date and time stamp is a long integer that represents the number of milliseconds since
midnight January 1st, 1970 You can query this value by calling getLastModified()
Whenever a request is made to modify the record store, it is done in one fell swoop That is, once a change is underway it is guaranteed to be completed before another begins If a request(s) is made to update the record store while an operation is in progress, it will be queued, waiting for the current operation to finish
Removing Record Stores
If a MIDlet suite is removed from a device, all record stores will be deleted as well
Record Store API
This class is the heart of the RMS Through this class we create, update, query and delete record stores (see Table 11.2)
Table 11.2 RecordStore Class: javax.microedition.rms.RecordStore
Constructors
No constructor See openRecordStore()
Methods
static RecordStore openRecordStore(String
recordStoreName, boolean createIfNecessary)
Open record store Optionally, create the store if it does not already exist
void closeRecordStore() Close record store
static void deleteRecordStore(String recordStoreName) Delete record store
static String[] listRecordStores() List record stores in MIDlet suite
int addRecord(byte[] data, int offset, int numBytes) Add a record
void setRecord(int recordId, byte[] newData, int offset, int
numBytes) record
Set (replace) data in a
void deleteRecord(int recordId) Delete a record
byte[] getRecord(int recordId) Get byte array containing record data
int getRecord(int recordId, byte[] buffer, int offset) Get contents of record into byte array
Trang 8parameter copying data from specified offset
int getRecordSize(int recordId) Size in bytes of a record
int getNextRecordID() The number of the next record when
adding a new record to the store
int getNumRecords() Number of records in the record store
long getLastModified() Last modified date of the record store
int getVersion() Record store version number
String getName() Name of the record store
int getSize() Total bytes occupied by record store
int getSizeAvailable() Current space (bytes) available for
records This will change as records are added/deleted
RecordEnumeration enumerateRecords( RecordFilter filter,
RecordComparator comparator, boolean keepUpdated)
Build an enumeration for traversing records in the record store
void addRecordListener (RecordListener listener) Add a listener to detect record store
changes
void removeRecordListener (RecordListener listener) Remove listener
Example: Read and Write Records
Now that we understand the basics of RMS, let's walk through two examples Each will create a record store, write several records, read back those same records and delete the record store The difference lies with how the data is manipulated before and after writing More on that in a minute
We have two methods to write data into a record store:
public int addRecord (byte[] data, int offset, int numBytes)
public void setRecord (int recordId, byte[] newData,
int offset, int numBytes)
Regardless of which we choose, each accepts an array of bytes as the input What will vary for our two examples is how we manage the array of data, for both reading and writing records
In our first example (Example 11.1), we will write String objects into the record store
Figure 11–2 shows the contents of the two records that were read from the record store
Figure 11-2 Reading and writing String objects
Trang 9I've gone ahead and written several convenience methods for managing the record store These include opening, closing and deleting Each method
private RecordStore rs = null;
static final String REC_STORE = "db_1";
public ReadWrite()
{
openRecStore(); // Create the record store
// Write a few records and read them back
writeRecord("J2ME and MIDP");
writeRecord("Wireless Technology");
readRecords();
closeRecStore(); // Close record store
deleteRecStore(); // Remove the record store
Trang 11* Simple message to console for debug/errors
* When used with Exceptions we should handle the
* error in a more appropriate manner
// The second parameter indicates that the record store
// should be created if it does not exist
appropriately manage these errors
Let's focus on the code to write and read records Inside writeRecord() we need to convert the
String object passed in to an array of bytes We can now pass this array as a parameter to
addRecord()
byte[] rec = str.getBytes();
rs.addRecord(rec, 0, rec.length);
Reading from the record store is not a great deal different Inside readRecords() we set up an array
of bytes to hold the record data and call getRecord()
byte[] recData = new byte[50];
Trang 12
len = rs.getRecord( i, recData, 0 );
Worth mentioning is the size of array that will hold the record data—I simply allocated an array that I knew was large enough It may not always be as simple as this If you did not write the records yourself, you may not be privy to the contents (length) of each record With a minor tweak we can make this code a little more robust Let's check the length of the data in the record before we actually read it from the store This will give us an opportunity to resize the destination array Change the for loop inside read-Records() to the following:
for (int i = 1; i <= rs.getNumRecords(); i++)
{
if (rs.getRecordSize(i) > recData.length)
recData = new byte[rs.getRecordSize(i)];
len = rs.getRecord(i, recData, 0);
System.out.println("Record #" + i + ": " +
new String(recData, 0, len));
System.out.println(" -");
}
Example: Read and Write Records with Streams
If you plan to read and write only text to the record store, at this point you've got all you need It may not be the most efficient or fastest way to manage data, but it does work There is a much preferred means to read and write data, which leads us to our next example
The code in Example 11.2 is similar to the previous example in that it writes and reads a few records
to the record store However, there are two main differences First, we'll write more than straight text Specifically, we will store Java data types including a String, int, and a boolean Second, we'll use Java streams for both reading and writing Not only will we have additional flexibility for writing any type of data, we also increase the efficiency with which data is written to and read from the record store
Example 11.2 ReadWriteStreams.java
/* -
* ReadWriteStreams.java
*
* Use streams to read and write Java data types
* to the record store
private RecordStore rs = null; // Record store
static final String REC_STORE = "db_1"; // Name of record store public ReadWriteStreams()
Trang 13{
openRecStore(); // Create the record store
writeTestData(); // Write a series of records
readStream(); // Read back the records
closeRecStore(); // Close record store
deleteRecStore(); // Remove the record store
String[] strings = {"Text 1", "Text 2"};
boolean[] booleans = {false, true};
Trang 14// Toss any data in the internal array so writes
// starts at beginning (of the internal array)
// Careful: Make sure this is big enough!
// Better yet, test and reallocate if necessary
byte[] recData = new byte[50];
// Read from the specified byte array
Trang 15for (int i = 1; i <= rs.getNumRecords(); i++)
* Simple message to console for debug/errors
* When used with Exceptions we should handle the
* error in a more appropriate manner
Before we see the output of this MIDlet, let's look over the code for writing to the record store First,
we create and populate several arrays for the data we want to write:
public void writeTestData()
{
String[] strings = {"Text 1", "Text 2"};
boolean[] booleans = {false, true};
// Write data into an internal byte array
ByteArrayOutputStream strmBytes = new ByteArrayOutputStream();
Trang 16// Write Java data types into the above byte array
DataOutputStream strmDataType = new DataOutputStream(strmBytes);
// Toss any data in the internal array so writes
// starts at beginning (of the internal array)
in the same format That is, if we write an integer into the byte array, regardless of how the system running the JVM represents an integer (16 bits, 32 bits, etc.), we are guaranteed to get the expected results
Once the streams are in place we call the appropriate methods based on the type of data we choose to write Next, we flush the stream, which writes any buffered data to the stream We're nearly there All
we have to do at this point is get the data from the stream into a byte array and add the record
There is one small, yet very important detail that we must take care of before we can write another record We need to clear the internal array that is being managed by our byte stream, strmBytes
Trang 17byte[] recData = new byte[50];
// Read from the specified byte array
ByteArrayInputStream strmBytes = new
ByteArrayInputStream(recData);
// Read Java data types from the above byte array
DataInputStream strmDataType = new DataInputStream(strmBytes);
for (int i = 1; i <= rs.getNumRecords(); i++)
DataInputStream Notice the declarations for each:
// Read from the specified byte array
ByteArrayInputStream strmBytes = new ByteArrayInputStream(recData); // Read Java data types from the above byte array
DataInputStream strmDataType = new DataInputStream(strmBytes);
The byte array stream, strmBytes, references the array (recData) that the record store will write data into The data input stream, strmDataType, which reads Java primitive types, references the byte stream We now have everything in place to read back our data just as it was written
System.out.println("UTF: " + strmDataType.readUTF());
System.out.println("Boolean: " + strmDataType.readBoolean());
System.out.println("Int: " + strmDataType.readInt());
The output for this MIDlet is shown in Figure 11–3
Figure 11-3 Reading and writing Java primitive data types
Trang 18Java Primitives
Although this seems quite obvious, when using DataOutputStream and
DataInputStream you need to write and read in the same order and format For
example, if you write a text string, a boolean value and an integer, you must read back a text
string, boolean value and an integer If you aren't getting the expected results when using
these streams, look carefully to see that you are reading and writing data in the same
sequence
Navigating with RecordEnumeration
In the previous example we perused records in the record store using a simple loop:
for (int i = 1; i <= rs.getNumRecords(); i++)
Setting up an enumerator takes only a few lines Assuming the variable rs references an existing and open record store, here is a loop to move through all the records:
RecordEnumeration re = rs.enumerateRecords(null,null,false);
while (re.hasNextElement())
{
// Get the next record into a String
String str = new String(re.nextRecord());
Trang 19do something
}
There are two key methods for moving through a record store with an enumerator: nextRecord() to move forward, previousRecord() to move back If you need to start at the end and move toward the front, create an enumerator and have as your first call previousRecord(), which will return the last record Each subsequent call will move you one element closer to the front
An enumerator maintains in internal index of the record store If at any point the record store changes,
it may be possible that the enumerator will no longer return the correct results As an example, assume that you create an enumerator to return records that are sorted At a later point in the application, a new record is added This new record may upset the balance and affect what the "new" proper sort order is The enumerator has a method to re-index, which will take into consideration any changes that may have occurred within the record store
As far as when to re-index:
1 You can make calls to reindex() whenever you update, delete or add a record This will work fine, as long as you are thorough and sure not to leave any holes, which may cause the enumerator to get out of sync
2 A record listener can be established to notify you of changes to the record store Whenever a change occurs, the listener will call one of three methods, depending on whether the change was an add, delete or update Within the method called, you can re-index the record store We'll discuss this in more detail in the next section
int numRecords() Number of records in the enumeration (result set)
byte[] nextRecord() Get the next record in result set
int nextRecordId() Get ID of the next record number in result set
byte[] previousRecord() Get previous record in result set
int previousRecordId() Get ID of previous record in result set
boolean hasNextElement() Does enumeration have more records going forward?
boolean hasPreviousElement() Does enumeration have more records going backward?
void keepUpdated(boolean
keepUpdated)
Set whether enumeration will re-index as record store changes
boolean isKeptUpdated() Will enumeration re-index as record store changes?
void rebuild() Rebuild enumeration index
void reset() Reset enumeration to beginning
void destroy() Free all resources held by the enumeration
Trang 20Sorting with RecordComparator
RecordComparator is a Java interface You implement this interface when you would like the enumerator to return records in sorted order
Here is a simple class that implements the interface
public class Comparator implements RecordComparator
{
public int compare(byte[] rec1, byte[] rec2)
{
String str1 = new String(rec1), str2 = new String(rec2);
int result = str1.compareTo(str2);
// Create a new comparator for sorting
Comparator comp = new Comparator();
// Reference the comparator when creating the result set
The enumerator references the compare() inside Comparator to sort the records in the record store
Table 11.4 RecordComparator Types: javax.microedition rms.RecordComparator
EQUIVALENT The records passed to compare() method are equivalent
FOLLOWS Based on the sort algorithm in the compare() method, the first parameter follows
the second PRECEDES Based on the sort algorithm in the compare() method, the first parameter precedes
the second
RecordComparator API
Table 11.5 RecordComparator Interface: javax.microedition.rms.RecordComparator
Trang 21Example: Simple String Sort
Let's head back to Example 11.1 and add a comparator class (see Example 11.3) for sorting the records in the record store
private RecordStore rs = null;
static final String REC_STORE = "db_1";
public SimpleSort()
{
openRecStore(); // Create the record store
// Write a few records
closeRecStore(); // Close record store
deleteRecStore(); // Remove the record store
Trang 23* Simple message to console for debug/errors
* When used with Exceptions we should handle the
* error in a more appropriate manner
String str1 = new String(rec1), str2 = new String(rec2);
int result = str1.compareTo(str2);
Figure 11-4 Sorted records using an Enumerator with RecordComparator
Trang 24It's obvious this example works just fine, so why does the enumerator re quire a separate class for comparing records? Would it be possible for the enumerator to have a string sorting algorithm all of its own? Well, it could, in theory
Inside the compare() method is a reference to compareTo() which is a method inside the String
class
int result = str1.compareTo(str2);
This is easy enough, assuming the data in a record is always text, as it was in this example However,
if you recall Example 11.2, we wrote several Java data types into each record The code looked
something similar to this:
// Write Java data types to stream
strmDataType.writeUTF("Text 1");
strmDataType.writeBoolean(true);
strmDataType.writeInt(1);
Each of these calls writes to a stream, in binary format The contents of the stream are then written to
an array, which is in turn added to a record in the record store
// Get stream data into an array
Example: String Sort with Compound Records
More often than not, you'll store multiple "fields" of data within a single record as we did in Example 11.2 (storing a String, boolean and int) When a record has multiple data types stored within it (what I'll refer to as a compound record), there may be more than one field that would be appropriate for sorting Reasonable sorting choices for Example 11.2 could be either the text or the integer It all depends on the needs of the application
Trang 25The next two examples will implement the RecordComparator interface and each will sort a record that contains multiple Java data types The examples will use the same set of data; however, one will sort based on a String, and one will sort based on an int To be consistent with Example 11.2, each record will consist of a String, boolean and int Here are the arrays that will make up each record:
String[] pets = {"duke", "tiger", "spike", "beauregard"};
boolean[] dog = {true, false, true, true};
We need to extract from each record the "field" that we wish to use as the sort criteria Example 11.4will extract and sort based on the String at the beginning of each record Example 11.5 will extract and sort based on the int at the end of each record
Example 11.4 StringSor t.java
/* -
* StringSort.java
*
* Sort records that contain multiple Java
* data types Sort using String type
private RecordStore rs = null; // Record store
static final String REC_STORE = "db_3"; // Name of record store
Trang 26{
openRecStore(); // Create the record store
writeTestData(); // Write a series of records
readStream(); // Read back the records
closeRecStore(); // Close record store
deleteRecStore(); // Remove the record store
Trang 27{
try
{
// Careful: Make sure this is big enough!
// Better yet, test and reallocate if necessary
byte[] recData = new byte[50];
// Read from the specified byte array
ByteArrayInputStream strmBytes =
new ByteArrayInputStream(recData);
// Read Java data types from the above byte array DataInputStream strmDataType =
Trang 28// Reset so read starts at beginning of array
* Simple message to console for debug/errors
* When used with Exceptions we should handle the
* error in a more appropriate manner
| Compares two strings to determine sort order
| Each record passed in contains multiple Java data
| types - use only the String data for sorting
* -*/