|
|
|
Contents: |
|
|
|
Related content: |
|
|
|
Subscriptions: |
|
|
| Persistent storage in J2ME and MIDP
John
Muchow (mailto:John@CoreJ2ME.com?cc=&subject=Inside
the Record Management System) Developer and Author 12 December
2003
MIDP does not use a filesystem to save
application data. Rather, MIDP stores all information in non-volatile
memory, using a storage system called the Record Management System
(RMS). In In this article, the first in a two-part companion series to
the J2ME 101 tutorial
series, author and developer John Muchow introduces the basics of the
RMS application interface, then walks you through several development
examples that illustrate its
functionality.
Data storage and retrieval is an essential aspect of application
development. The data that is stored will depend on the type and
complexity of the application. In some cases the only persistent data to
be stored is the application user preferences. In other cases you may need
to store and manage a repository of contact information. On the high end,
you could be designing a data storage solution for a full range of
supply-chain data. No matter what the case, there is just one all-purpose
data storage and management solution for MIDP-enabled devices, and that is
the Record Management System (RMS).
In this article, the first of a companion series to the two-part J2ME 101 tutorial
series, we'll explore the inner workings of MIDP's persistent storage
system. We'll start with a quick overview of RMS, but most of the article
(like the tutorial series) will be hands-on. Together, we'll build MIDlets
that will help you understand how data records are written to and read
from the RMS, as well as the variety of sorting, searching, and retrieval
options that are available within this remarkably well-rounded and compact
data management system.
Please note that this article assumes that you are familiar with MIDlet
development in the J2ME environment. You will need to have a J2ME
development environment installed on your system in order to compile the
code examples. See the Resources section for
links to installation instructions for the J2ME Wireless Toolkit
(WTK).
Record management in
MIDP Put simply, the MIDP Record Management System (RMS)
provides a means to store application data that persists across
invocations of a MIDlet. You can visualize the RMS record store as a very
simple database, where each row consists of two columns: one containing a
unique row identifier, the other a series of bytes representing the data
in the record. Table 1 illustrates a simple record store database.
Table 1. A record store
database
Record ID |
Data |
1 |
Array of bytes |
2 |
Array of bytes |
3 |
Array of bytes |
... |
... |
The unique row identifer is an integer value. The first entry will have
the ID of 1, the next of 2, and so on. Row identifiers are not re-used if
a row is deleted. That is, if we have three rows in a table with the IDs
1, 2, and 3, deleting ID 2 will remove this identifier permanently from
the record store. If we were to add another row to this table, it would
have an identifier of 4.
Record stores are identified by name. A name may consist of up to 32
characters, and all characters are case sensitive. No two record stores
within a MIDlet suite (that is, a collection of one or more MIDlets
packaged together) may contain the same name.
Each record store has a version number as well as a date/time stamp.
Both values are updated whenever a record is added, replaced, or
deleted.
Creating a record
store No constructor exists for creating a record store.
Instead, we use a set of three dual-purpose methods to create and/or open
record stores. Each method is shown in Listing 1. Listing 1. Creating and opening record stores
RecordStore openRecordStore(String recordStoreName,
boolean createIfNecessary)
RecordStore openRecordStore(String recordStoreName,
boolean createIfNecessary,
int authmode,
boolean writable)
RecordStore openRecordStore(String recordStoreName,
String vendorName,
String suiteName)
|
Deleting a record store When a
MIDlet suite is deleted from a device, any record stores created by
the suite will also be removed. |
The first method opens the named record store if it exists. If the
named record store does not exist, and if the parameter
createIfNecessary is set to true , the method can
be used to create a new (named) record store. The second method operates
in the same fashion but employs two additional parameters to specify the
record store's access restrictions. The first parameter specifies whether
or not only those MIDlets in the same suite can access the record store.
The second one specifies whether MIDlets that have access to the record
store, can create new records. The last method provdes a means for a
MIDlet to open a record store in another MIDlet suite.
The RecordStore
API A handful of methods are available for working with a
record store. These range from adding, deleting, and replacing record
contents to enumerating through a record store. Each available method is
shown in Listing 2. Listing 2. Record store
methods
void closeRecordStore()
void deleteRecordStore(String recordStoreName)
String[] listRecordStores()
int addRecord(byte[] data, int offset, int numBytes)
void setRecord(int recordId, byte[] newData, int offset, int numBytes)
void deleteRecord (int recordId)
byte[] getRecord (int recordId)
int getRecord (int recordId, byte[] buffer, int offset)
int getRecordSize (int recordId)
int getNextRecordID()
int getNumRecords()
long getLastModified()
int getVersion()
String getName()
int getSize()
int getSizeAvailable()
RecordEnumeration enumerateRecords(RecordFilter filter,
RecordComparator comparator,
boolean keepUpdated)
void addRecordListener (RecordListener listener)
void removeRecordListener (RecordListener listener)
void setMode(int authmode, boolean writable)
|
Locking a record Unlike a
traditional database, RMS offers no methods to lock a record store.
It is up to the device implementation to ensure that all operations
are synchronous. Should you use separate threads to access a record
store, it is up to you to ensure that threads accessing resources do
not interfere with each other. |
You'll learn more about the RecordStore API and many of
its methods as we work through the examples in the sections that follow.
The ReadWrite
MIDlet Our first illustrative example is the ReadWrite
MIDlet. This MIDlet has the ability to create a record store, write
several records into persistent storage, read back those same records, and
delete the record store upon exiting. As you study the code below, note
that the MIDlet contains several "convenience" methods, which are methods
used time and again when working with the RMS. Convenience methods are the
methods used to open, close, and delete record stores.
Take a look at the complete code for the ReadWrite MIDlet and
then we'll discuss it in more detail.
Notes about the
code A couple of points are worth mentioning before we move
on. First, note that in this example we created a new record store by
passing true for the createIfNecessary parameter
when calling RecordStore.openRecordStore(REC_STORE, true) , as
previously explained.
Second, when we wrote to the record store with writeRecord(String
str) , we first converted the Java string parameter into a byte
array. This byte array was then passed to addRecord(rec, 0,
rec.length) to insert a record into the record store.
Finally, in the readRecords() method we allocated a byte
array to store any record data we read from the RMS. This allows us to
make a specific check on each read to ensure that the array is large
enough to hold the data. Once the record has been retrieved, we can output
the contents to the console.
Figure 1 shows the output of the ReadWrite MIDlet when it is run from
within the J2ME WTK.
Figure 1. Record output from the ReadWrite
MIDlet
Read and write Java
primitives The ReadWrite MIDlet only writes text strings to
the record store. For the next example, we'll add the ability to store and
manipulate arrays of integer, boolean, and string values. We'll also add
support for reading and writing using Java streams. As with the previous
example, we'll write several records and then read them back from the
store.
As with any MIDlet that needs to access the record store, we begin by
using the openRecordStore() method to allocate and open (or
create) a record store, as shown in Listing 3. Listing 3. Create a record store
private RecordStore rs = null; // Record store
...
public void openRecStore()
{
...
// Create record store if it does not exist
rs = RecordStore.openRecordStore(REC_STORE, true);
...
}
|
Listing 4 shows the primitive data types we'll write to the record
store. Listing 4. Java primitive data
types
public void writeTestData()
{
boolean[] booleans = {true,false};
int[] integers = {17 , 4};
String[] strings = {"Golf", "Tennis"};
writeStream(booleans, integers, strings);
}
|
If you're familiar with using streams in the Java language, you'll find
little difference between working with a MIDlet and with a more
traditional Java application. The steps are as follows:
- Allocate streams
- Write the data
- Flush the stream
- Transfer the stream data into an array
- Write the array to the record store
- Close the streams
Listing 5 shows how to write to RMS using a stream: Listing 5. Writing a stream to the record
store
public void writeStream(boolean[] bData, int[] iData, String[] sData)
{
try
{
// Write data into an internal byte array
ByteArrayOutputStream strmBytes = new ByteArrayOutputStream();
// Write Java data types into the above byte array
DataOutputStream strmDataType = new DataOutputStream(strmBytes);
byte[] record;
for (int i = 0; i < sData.length; i++)
{
// Write Java data types
strmDataType.writeBoolean(bData[i]);
strmDataType.writeInt(iData[i]);
strmDataType.writeUTF(sData[i]);
// Clear any buffered data
strmDataType.flush();
// Get stream data into byte array and write record
record = strmBytes.toByteArray();
rs.addRecord(record, 0, record.length);
// Toss any data in the internal array so writes
// starts at beginning (of the internal array)
strmBytes.reset();
}
strmBytes.close();
strmDataType.close();
}
catch (Exception e)
{
db(e.toString());
}
}
|
Next we want to read from the record store. We begin by creating the
necessary input streams, then loop through each record, storing its
contents in a byte array. Accessing the data input stream we read each
Java primitive type from the byte array and print the associated contents
to the console, as shown in Listing 6. Listing 6.
Reading a stream from the record store
public void readStream()
{
try
{
// Allocate space to hold each record
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 = new DataInputStream(strmBytes);
for (int i = 1; i <= rs.getNumRecords(); i++)
{
// Get data into the byte array
rs.getRecord(i, recData, 0);
// Read back the data types
System.out.println("Record #" + i);
System.out.println("Boolean: " + strmDataType.readBoolean());
System.out.println("Integer: " + strmDataType.readInt());
System.out.println("String: " + strmDataType.readUTF());
System.out.println("--------------------");
// Reset so read starts at beginning of array
strmBytes.reset();
}
strmBytes.close();
strmDataType.close();
}
catch (Exception e)
{
db(e.toString());
}
}
|
Now check out the source code for the ReadWritePrimitives
MIDlet. Study it carefully, and then run the code in your WTK emulator
to view the output if you like.
The console output for the ReadWritePrimitives MIDlet should look as
shown in Figure 2.
Figure 2. Output of the ReadWritePrimitives
MIDlet
The RecordEnumeration
API Up to this point we've been traversing the record store
with a simple for loop. Although this technique has been
sufficient to our needs so far, we'll see in this section what happens
when we extend our code to include a record enumerator. One of the primary
benefits of incorporating a record enumerator into your code is that it
includes options for searching the record store, and for retrieving
records in sorted order.
Listing 7 shows the methods for the RecordEnumeration API. Listing 7. The RecordEnumeration API
int numRecords()
byte[] nextRecord()
int nextRecordId()
byte[] previousRecord()
int previousRecordId()
boolean hasNextElement()
boolean hasPreviousElement()
void keepUpdated(boolean keepUpdated)
boolean isKeptUpdated()
void rebuild()
void reset()
void destroy()
|
Incorporating enumeration into your code is as easy as creating a
RecordEnumeration object from an open record store and
looping through each record. Listing 8 shows how to create a
RecordEnumeration . The first two parameters (which are both
null in this example) specify the classes that carry out
searching and/or sorting on the record store. The third parameter is the
keepUpdated flag as defined in the API. When this variable is
set to true the enumeration result set will be re-initialized
whenever the record store is changed. Setting the parameter to
false specifies the enumeration to ignore record store
updates. Listing 8. Create a
RecordEnumeration
rs = RecordStore.openRecordStore(REC_STORE, true);
...
RecordEnumeration re = rs.enumerateRecords(null, null, false);
while (re.hasNextElement())
{
// Get next record
String str = new String(re.nextRecord());
...
}
|
Why record enumeration
counts At first the enumeration loop seems to provide
essentially the same functionality as our for loop did, but
in fact it offers a lot more functionality. In addition to providing a
means for searching and sorting (which we'll get to momentarily), record
enumeration overcomes an essential (although not immediately apparent)
disadvantage of using the for loop.
From the beginning of the article, you may recall that record IDs are
not re-used in a record store. Therefore, if a store has three records
with IDs 1, 2, and 3 and record ID 2 is deleted, the store will consist of
IDs 1 and 3. This isn't a problem until you consider how we've been
looping through the record store. The code in Listing 9 will refresh your
memory. Listing 9. A typical for loop to read
records
byte[] recData = new byte[5];
int len;
for (int i = 1; i <= rs.getNumRecords(); i++)
{
// Allocate more storage if necessary
if (rs.getRecordSize(i) > recData.length)
recData = new byte[rs.getRecordSize(i)];
len = rs.getRecord(i, recData, 0);
}
|
As you've probably figured out, we'll hit trouble with that last line
of code. Although getNumRecords() will return the correct
number of records (two), the values for the variable i will
be 1 and 2, as returned by the for loop. But the record IDs
in the store are 1 and 3. The call to rs.getRecord() will
fail when i equals 2, as we no
longer have a record ID with that value. It is here that record
enumeration comes to our rescue, since it doesn't obtain records by
ID.
Can you spare an update? Use
caution when specifying the keepUpdated parameter as
true when creating an enumerator. Although the record
store result set will be current with changes to the store,
continuous updates may cause a performance
bottleneck. |
The RecordComparator
API The RecordEnumeration API provides the
foundation for working with enumerations, allowing us to cycle through
entries in the RMS. But it's the RecordComparator interface
that lets us actually retrieve records from the record store in sorted
order.
The RecordComparator API consists of one method and three
pre-defined return values. The sole method in this interface accepts two
parameters, and both are byte arrays. In Listing 10, you can see how these
arrays represent records from the record store. The enumeration calls the
compare() method passing in the contents of two records from
the store. Working through the entire record store, the enumeration and
our search code build a result set that is sorted based on the specified
search criteria. Listing 10. RecordComparator
API
int compare(byte[] rec1, byte[] rec2)
static int EQUIVALENT
static int FOLLOWS
static int PRECEDES
|
Listing 11 shows a simple class implementing the
Comparator interface to sort records based on comparing the
string contents of each record. Notice that the return value must be one
of the pre-defined values, EQUIVALENT , PRECEDES ,
or FOLLOWS . The comparator class is followed by the code to
create an instance of the comparator and pass this as a parameter when
creating a record enumeration. Listing 11. A
Comparator class for sorting
//********************************************************
// Create comparator class for sorting
//********************************************************
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);
if (result == 0)
return RecordComparator.EQUIVALENT;
else if (result < 0)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
}
}
...
//********************************************************
// How to access the comparator using a record enumeration
//
// Note: Variable 'rs' is created outside the scope of
// this method
//********************************************************
// Create a new comparator for sorting
comparator comp = new comparator();
// Reference the comparator when creating the result set
RecordEnumeration re = rs.enumerateRecords(null, comp, false);
// Retrieve each record in sorted order
while (re.hasNextElement())
{
String str = new String(re.nextRecord());
...
}
|
Sorting multiple data
types Clearly, sorting records that consist solely of string
data is a piece of cake. Working with a record store that contains
multiple data types in each record presents a little more of a challenge,
however. For example, refer back to Listing 4. In this
example we wrote three primitive Java data types into each record, a
boolean, integer, and string. So, how do we go about sorting based on one
of the specific data types? In order to accomplish this our comparator
function will need to extract the appropriate field to compare on, perform
the comparison, and return the appropriate value (EQUIVALENT ,
PRECEDES , or FOLLOWS ) to the enumerator.
To better understand how all this works, let's write a MIDlet that
sorts based on an integer. We'll start by updating the
writeTestData() method from Listing 4 so that each
record in the record store contains both string data and an integer, as
shown below: Listing 12. Java primitive data types
for sorting
public void writeTestData()
{
String[] strings = {"Java", "J2ME", "C"};
int[] integers = {2, 1, 3};
writeStream(strings, integers);
}
|
The output of the IntegerSort MIDlet is shown in Figure 3. Notice that
the comparator function (which we'll write shortly) returns records that
have been sorted based on the integer value stored within each record.
Figure 3. Records sorted by integer
Building a
comparator For the most part, the code for the IntegerSort
MIDlet (see the complete source code)
won't differ much from that of our previous MIDlets. The biggest change is
the addition of a comparator class (Listing 13) and the methods for
extracting the appropriate fields and performing the actual sorting.
Following is the ComparatorInteger class that handles all the
details for the IntegerSort MIDlet. Listing 13. The
ComparatorInteger class
/*--------------------------------------------------
* Compares two integers to determine sort order
* Each record passed in contains multiple Java data
* types - use only the integer data for sorting
*-------------------------------------------------*/
class ComparatorInteger implements RecordComparator
{
private byte[] recData = new byte[10];
// Read from a specified byte array
private ByteArrayInputStream strmBytes = null;
// Read Java data types from the above byte array
private DataInputStream strmDataType = null;
public void compareIntClose()
{
try
{
if (strmBytes != null)
strmBytes.close();
if (strmDataType != null)
strmDataType.close();
}
catch (Exception e)
{}
}
public int compare(byte[] rec1, byte[] rec2)
{
int x1, x2;
try
{
// If either record is larger than our buffer, reallocate
int maxsize = Math.max(rec1.length, rec2.length);
if (maxsize > recData.length)
recData = new byte[maxsize];
// Read record #1
// We want the integer from the record, which is
// the second "field" thus we must read the
// String first to get to the integer value
strmBytes = new ByteArrayInputStream(rec1);
strmDataType = new DataInputStream(strmBytes);
strmDataType.readUTF(); // Read string
x1 = strmDataType.readInt(); // Read integer
// Read record #2
strmBytes = new ByteArrayInputStream(rec2);
strmDataType = new DataInputStream(strmBytes);
strmDataType.readUTF(); // Read string
x2 = strmDataType.readInt(); // Read integer
// Compare record #1 and #2
if (x1 == x2)
return RecordComparator.EQUIVALENT;
else if (x1 < x2)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
}
catch (Exception e)
{
return RecordComparator.EQUIVALENT;
}
}
|
Pay close attention to the code for reading the Java primitives. We
need to retrieve the integer value from each record. However, this value
is the second "field" stored in each record. Therefore, we simply read the
string (UTF) value and toss it aside. The second read stores the integer
value in a local variable (x1 or x2). These values are then compared to
determine the proper sort order. Listing 14.
Reading and sorting Java primitives
// Read record #1
...
strmDataType.readUTF(); // Read string
x1 = strmDataType.readInt(); // Read integer
// Read record #2
...
strmDataType.readUTF(); // Read string
x2 = strmDataType.readInt(); // Read integer
// Compare record #1 and #2
if (x1 == x2)
return RecordComparator.EQUIVALENT;
else if (x1 < x2)
return RecordComparator.PRECEDES;
else
return RecordComparator.FOLLOWS;
|
Building an
enumerator With the comparator code written, we next create
an enumerator, referencing an instance of the
ComparatorInteger class. The enumerator will create a result
set of records from the record store, using the comparator as the sorting
algorithm. Listing 15 is a partial listing of the
readStream() method which creates the comparator as well as
the enumerator, and loops through the result set displaying record
contents on the console. Listing 15. readStream()
method
public void readStream()
{
...
if (rs.getNumRecords() > 0)
{
// Create instance of the comparator
ComparatorInteger comp = new ComparatorInteger();
// Create enumerator, referencing the comparator
RecordEnumeration re = rs.enumerateRecords(null, comp, false);
// Loop through all elements in the result set
int i = 1;
while (re.hasNextElement())
{
// Get data into the byte array
rs.getRecord(re.nextRecordId(), recData, 0);
// Read back the data types
System.out.println("Record #" + i++);
System.out.println("String: " + strmDataType.readUTF());
System.out.println("Integer: " + strmDataType.readInt());
System.out.println("--------------------");
// Reset so read starts at beginning of array
strmBytes.reset();
}
...
}
|
The RecordFilter
API Sorting with a comparator is one option when working
with an enumerator, searching with a filter is the other. A minor
difference between a comparator and a filter is that a comparator returns the
entire record store in sorted order, whereas a filter returns only
those records that match a specified criteria. If you apply both a
comparator and a filter, records that match the search criteria will be
returned in sorted order.
Like the RecordComparator , the RecordFilter
is implemented by the addition of a single method, matches() ,
to the enumerator code. The enumerator calls thematches()
method for each record in the store. Based on the boolean return value,
the record either becomes a member of the result set or is tossed aside as
a record that does not meet the search criteria. Listing 16. The RecordFilter API's matches()
method
boolean matches(byte[] candidate)
|
Building a
filter Listing 17 shows a class that implements the
RecordFilter interface. Notice that the search string is
specified as a parameter to the constructor SearchFilter . The
string is saved in a private variable so we have access to the string when
the enumerator calls the matches() method to create the
result set. For this example, the search string is also converted to
lowercase, resulting in a search that is not
case-sensitive. Listing 17. Building a
RecordFilter
//********************************************************
// Create filter class for searching
//********************************************************
class SearchFilter implements RecordFilter
{
private String searchText = null;
public SearchFilter(String searchText)
{
// Text to find
this.searchText = searchText.toLowerCase();
}
public boolean matches(byte[] candidate)
{
String str = new String(candidate).toLowerCase();
// Does the text exist?
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
}
}
...
//********************************************************
// How to access the filter using a record enumeration
//
// Note: Variable 'rs' is created outside the scope of
// this method
//********************************************************
// Create search filter
SearchFilter search = new SearchFilter("abc");
// Reference filter when creating the result set
RecordEnumeration re = rs.enumerateRecords(search, null, false);
// If there is at least one record in result set, a match was found
if (re.numRecords() > 0)
{
// At least one record in the result set, do something here...
...
}
|
Note that the actual code that performs the search in this example is
quite trivial. Using the Java string method indexOf() , we
look for the specified search string and return a boolean value indicating
success or failure.
The StringSearch
MIDlet We'll build one last MIDlet to illustrate what you've
learned so far. (Here's the complete source for the StringSearch MIDlet.) In
addition to allowing us to search the RMS for records, this MIDlet beefs
up the user interface we've worked with so far. Instead of using only the
console for output, we'll display a TextField component
(which you should recall from the tutorial series!) that will prompt the
user for a text string. When requested, we'll search the record store for
the string. Any and all matches will be appended on the display, showing
the search results.
Listing 18 shows the search strings that will be written into the
record store. Listing 18. Entries to the record
store
public void writeTestData()
{
String[] strs = {
"I think this would be a good time for a beer. (FDR)",
"I'll make it a felony to drink small beer. (Shakespeare)",
"They who drink beer will think beer. (Washington Irving)",
"I would give all my fame for a pot of ale. (Shakespeare)"};
writeRecords(strs);
}
|
Figure 4 shows the MIDlet with two different search results.
Figure 4. The record search results
Creating the user interface consists of allocating a Form ,
a TextField , and two Command s -- one to search
the record store and one to exit the MIDlet, as shown in Listing 19. Listing 19. Create the user interface
components
...
// Define textfield, stringItem and commands
tfFind = new TextField("Find", "", 12, TextField.ANY);
cmExit = new Command("Exit", Command.EXIT, 1);
cmFind = new Command("Find", Command.SCREEN, 2);
// Create the form, add commands
fmMain = new Form("Record Search");
fmMain.addCommand(cmExit);
fmMain.addCommand(cmFind);
// Append textfield and stringItem to form
fmMain.append(tfFind);
...
|
Event
processing Once the StringSearch MIDlet is
active, all events will be processed inside the
commandAction() method. When the command cmFind
is requested, we call searchRecordStore() to begin the
process of searching. This consists of allocating an instance of our
SearchFilter() class and associating this instance with a
record enumeration object. When the enumeration object is allocated, it
will call our search function to search the record store. Any records that
match the search string will become part of the enumeration result
set.
Listing 20 shows the code to process events and search records for a
match against a user-entered text string. Notice the reference to
tfFind.getString() when creating the search filter. This call
obtains the user-entered search string from the textfield. The string is
stored inside the SearchFilter class in the variable
searchText . When the enumerator calls the method
matches() -- once for each record in the record store -- it
will search the active record for a string that matches
searchText . Listing 20. Processing a
user event
public void commandAction(Command c, Displayable s)
{
if (c == cmFind)
{
searchRecordStore();
}
else if (c == cmExit)
{
destroyApp(false);
notifyDestroyed();
}
}
...
//********************************************************
// Search the record store
//********************************************************
private void searchRecordStore()
{
try
{
// Record store is not empty
if (rs.getNumRecords() > 0)
{
// Setup the search filter with the user requested text
SearchFilter search = new SearchFilter(tfFind.getString());
RecordEnumeration re = rs.enumerateRecords(search, null, false);
// Remove any previous record entries displayed on the form
clearForm();
// A match was found using the filter
if (re.numRecords() > 0)
{
// Append all records found onto the form
while (re.hasNextElement())
fmMain.append(new String(re.nextRecord()));
}
re.destroy(); // Release enumerator
}
}
catch (Exception e)
{
db(e.toString());
}
}
...
//********************************************************
// Called for each record when creating the enumerator.
// Checks to see if the record contains text that
// matches the text string entered by the user.
//********************************************************
class SearchFilter implements RecordFilter
{
private String searchText = null;
public SearchFilter(String searchText)
{
// Text to find
this.searchText = searchText.toLowerCase();
}
public boolean matches(byte[] candidate)
{
String str = new String(candidate).toLowerCase();
// Look for text
if (searchText != null && str.indexOf(searchText) != -1)
return true;
else
return false;
}
}
|
The RecordListener
API The RecordListener interface is the last
API we'll discuss, but not the least important one. Implementing a
RecordListener in your code ensures that you will be notified
of changes, additions, or deletions to the record store.
Here are the basic steps for working with a
RecordListener :
- Open (create) a record store.
- Create a new listener
- Implement each method in the
RecordListener
interface
For each method in the RecordListener API, the parameters
passed in are the same: a reference to the record store upon which the
change has occurred and the record ID that was affected. Listing 21 shows
the methods for the RecordListener API. Listing 21. The RecordListener API
void recordAdded(RecordStore recordStore, int recordId)
void recordChanged(RecordStore recordStore, int recordId)
void recordDeleted(RecordStore recordStore, int recordId)
|
The short code block in Listing 22 demonstrates how you can display a
message to the console upon any adding, deleting, or changing a record in
the RMS. Listing 22. Creating a record
listener
// Open record store
rs = RecordStore.openRecordStore(REC_STORE, true);
...
// Using handle to open record store, create a listener
rs.addRecordListener(new DemoRecordListener());
...
//********************************************************
// Listener to process updates to the record store
//********************************************************
class DemoRecordListener implements RecordListener
{
public void recordAdded(RecordStore recordStore, int recordId)
{
System.out.println("Record added");
}
public void recordDeleted(RecordStore recordStore, int recordId)
{
System.out.println("Record deleted");
}
public void recordChanged(RecordStore recordStore, int recordId)
{
System.out.println("Record changed");
}
|
Conclusion In this
third installment in the J2ME 101 series, you've
learned how to create and manage data records in your MIDP applications.
The convenience methods introduced at the beginning of the article will
let you write and read records into the record store. The
RecordEnumeration class enables you to move among all records
in the RMS. When combined with the RecordComparator it can
return records in sorted order, and when combined with a
RecordFilter it can search for specific records. If you
combine all three -- the enumerator, comparator, and filter -- you can
search for specific records and return them in sorted order. The
RecordListener , the last-but-not-least API we discussed, can
be used to set up event notification for your applications.
We'll close this series in a couple of weeks, with a comprehensive
introduction to network support in MIDP, so stay tuned!
Resources
- Download the source code for the
MIDlets in this article.
- Download Sun Microsystems's J2ME Wireless Toolkit
to compile the code samples.
- Not sure how to load that thing? The author's tutorial, "MIDletdevelopment with the
Wireless Toolkit" (developerWorks, March
2003) will get you started.
- If you missed the J2ME 101 series, start
now to get a leg up on :
- Part
1 provides an introduction to the MIDP high-level interface.
- Part
2 details MIDP's low-level interface.
- Part
4 examines the Generic Connection Framework.
- For a more DIY (do-it-yourself) intro to MIDP, turn to
java.sun.com's MIDP home
page.
- Soma Ghosh's "J2ME record management
store" (developerWorks, May
2002) is another good introduction to RMS.
- Learn a little more about the inner RMS, with the two-part series on
MicroDevNet, also by
the author.
- The WebSphere Micro
Environment provides an end-to-end solution connecting cellular
phones, PDAs, and other pervasive devices to e-business.
- The alphaWorks Web Services Toolkit for
Mobile Devices provides tools and a runtime environments for
developing applications that use Web services on small mobile devices,
gateway devices, and intelligent controllers.
- Core J2ME (Prentice
Hall PTR, 2002) by John W. Muchow is a comprehensive guide to J2ME
development. You can also visit the Core J2ME Web site for
additional articles, tutorials and developer resources.
- The developerWorks
Wireless zone offers a wealth of technical content on pervasive
computing.
- You'll find hundreds of articles about every aspect of Java
programming in the developerWorks Java
technology zone.
- Also see the Java technology zone
tutorials page for a complete listing of free Java-related tutorials
from developerWorks.
About the
author John Muchow, author of Core J2ME technology
and MIDP, is a freelance technical writer and developer, with
extensive experience in J2ME, Java, JSP, C and ColdFusion. Visit Core J2ME for
additional source code, articles, and developer resources. Send John
e-mail for
additional information about writing or software development
projects. |
|
|