HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

In Part 1 of this article, which can be found in JDJ (Vol. 6, issue 7), we covered the basics of creating a Mobile Information Device Profile application (also called a MIDlet). We covered some of the functionality available in the user interface packages and a slightly more advanced graphics example.

Missing from that discussion was one of the more unusual APIs included in MIDP - the Record Management System - which can be found in the package javax.microedition.rms. This is a persistent storage system based on a "simple record-oriented database." If you want to store data on a device (and not on the server, for example), you'll need to become very familiar with RMS.

The most fundamental object in RMS is the javax.microedition.rms.RecordStore, which is owned by a particular suite of applications currently installed on the device. What this means is that all the applications in the suite can share the same data, if necessary - games, for example, can store their saved state in one RecordStore. A phone book application might share its data with a simple scheduling application - as long as it's in the same suite, of course.

To open (or create) a new RecordStore, use a static method found in the RecordStore class. Assuming we wanted to write a Phone Book MIDlet, we would probably call our store "PhoneBook".

RecordStore store = RecordStore.openRecordStore("PhoneBook",true);
The Boolean value "true" is used to specify that the store will be created, if necessary.

A RecordStore has a number of methods that can be applied to it. The most useful of these are:

  • addRecord(...): Add a new record to the store
  • deleteRecord(...): Delete a record from the store
  • getNumRecords(...): Get the number of records in the store
  • getRecord(...): Get a copy of data in a record
  • getSize(): Get the size of the record store (in bytes)
  • getSizeAvailable(): Get the available space for the store to grow (also in bytes)
  • setRecord(...): Set the data in a specified record
Enumeration Is Not the Same as Remuneration
Use a RecordEnumeration to move back and forth through the store and view the data:
RecordEnumeration recordEnum = store.enumerateRecords(null, pbcomp, false);
Where the first argument (null) is the filter that specifies the subset of records that will be enumerated, the second argument (pbcomp) is a Record- Comparator (more on that soon) that specifies the order, and the final argument (false) specifies that the enumeration will, in this case, not be kept up-to-date with the contents of the store (in other words, the enumeration is a copy of the store at that point).

A RecordComparator, as mentioned before, is used to order an enumeration. Create a comparator by implementing the RecordComparator interface. The interface describes a single method (compare) that returns an integer value depending upon whether the contents of one byte array FOLLOWS, PRECEDES, or is EQUIVALENT to a second byte array (see Listing 1). Listings 1-7 can be found below. Now that we have an enumeration of records, what can we do with it? The important methods in the RecordEnumeration are:

  • hasNextElement(): Does the enumeration have more elements?
  • hasPreviousElements(): Are there elements prior to the current position?
  • nextRecord(): Get a copy of the data in the next record
  • nextRecordId(): Get the recordId of the next record
  • numRecords(): Get the number of records in the enumeration
  • PreviousRecord(): Get a copy of the data in the previous record
  • PreviousRecordId(): Get the recordId of the previous record
In the case of our aforementioned PhoneBook application, we might display the contents of the phone book on a canvas and use the up and down buttons to scroll back and forth through the enumeration dataset. On the canvas we could trap the key press and then pass the game action (game actions are special events, such as pressing an arrow button, which are mapped to particular keys) back to the parent application (containing the data store) to handle:
public void keyPressed(int k) {
parent.processAction(getGameAction(k));
}
In the parent class (in this case the MIDlet), the processAction() method will move forward and backward in the enumeration depending upon whether the up or down arrow has been pressed. For example:
if (action == Canvas.UP && recordEnum.hasPreviousElement()) {
currentRecordID = recordEnum.previousRecordId();
set(store.getRecord(currentRecordID));
}
else if (action == Canvas.DOWN && recordEnum.hasNextElement()) {
currentRecordID = recordEnum.nextRecordId();
set(store.getRecord(currentRecordID));
}
In this case, currentRecordID is an int that holds the recordId the enumeration is currently pointing to, and set(...) is a user-defined method that calls the canvas and changes the display accordingly.

Listing 2 provides the full method source; Listing 3 provides the source of the canvas class.

Large Mouthfuls, or Multiple Bytes
The records in a store are byte arrays, meaning you store and retrieve your records as simple byte arrays. So you can either work with the raw bytes (as I've done in my PhoneBook application, for simplicity's sake), or use a combination of ByteArray and data streams. Obviously, unless you have a requirement for fixed data sizes (which is inefficient, but again that's what I'm using in the PhoneBook), the stream combination will probably be the more efficient way to go, in terms of the most effective use of storage space.

It has the added advantage that using the various read methods in a DataInputStream (for example, readInt, readLond, readShort, readUTF), keep you one step away from the messy details of how that data is actually stored in the byte array.

To use the stream combination, you'll probably do something like the following:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream (baos);
dos.writeUTF("Hello");
dos.writeUTF("Test");
dos.writeInt(100);

byte rec[] = baos.toByteArray();

In comparison, the save() method in PhoneBook.java uses a fixed length name and variable length phone number, creating byte-arrays of the data using a specially defined function called rpad (which adds spaces on the end of the name if it's not long enough) (see Listing 4).

I leave it up to the individual to decide which way works better for them. One of the joys of Java is that while it provides functionality to insulate developers from any "nastiness," it also allows you to get in there and "do things your own way."

Figures 1-3 show the main screens in the PhoneBook MIDlet.

Figure 1
Figure  1:
Figure 2
Figure  2:
Figure 3
Figure  3:

Networking: It's More than Making Friends and Influencing People
MIDP has to include networking support. A large number of Java developers in lynch-mode, with nooses made from coaxial cable and baseball bats molded out of melted-down network cards, would turn up on Sun's doorstep in Palo Alto baying for blood if a Java product was released without adequate network support, and Sun knows that. Accordingly, you can find the various network IO classes in the javax.microedition.io package. However, network connectivity is handled differently from what you might be used to in the Standard Edition of Java (in java.net.*). One major difference is that instead of creating/opening a socket or a URLConnection and then getting an input stream from that object, you use the Connector class to create and open a Connection, an Input/OutputStream, or a DataInput/DataOutputStream. For example:

HttpConnection c = (HttpConnection)Connector.open("http://.................");
There are a number of Connection interfaces defined in javax.microedition.io: Connection, ContentConnection, DatagramConnection, HttpConnection, InputConnection, OutputConnection, StreamConnection and StreamConnectionNotifier.

In Listing 5 we'll look at the HttpConnection interface using a MIDlet called HttpTest.java. In this application we'll define a test method that takes a URL string as a parameter and then opens an HTTP POST connection to that URL (for more information on HTTP see RFC 2616 - www.ietf.org/rfc/rfc2616.txt?number=2616).

Looking at Listing 5 line by line: Lines 1 and 2 declare the HttpConnection object and the InputStream. Line 5 opens an HttpConnection to the supplied URL. Line 6 sets the request method on the connection. In this case we want to perform HTTP POST operations. Line 8 opens an input stream to the connection. Line 10 tries to get the content length. If a content length is provided, lines 12 and 13 create a byte array and then read from the stream into the array. If no length was provided, lines 18-22 read from the stream, character by character, until a terminator is reached. Lines 15 and 23 return the result from either set of operations. Finally, lines 27-30 and 32-35 close the InputStream and Connection.

I think you'll agree that this is fairly painless networking (as with most communications in Java).

What else is included in this MIDlet? HttpTest creates a simple list of five tests that's used as the first screen the user sees. When the user selects a test, and then selects the OK command, the code in Listing 6 is executed.

In Listing 6 lines 2-5 destroy the application if the Exit command was selected. Line 7 checks the current state, which is used to switch between the two screens, and line 8 changes the display to a TextBox (tb), which will be used to show the output. Line 11 gets the currently selected index in a list of tests that can be executed. Line 13 checks that the test selected is valid. Line 14 calls the test method with the URL of a TestServlet and passes a parameter action, which will be in the form, http://localhost:8080/servlet/TestServlet?action=n (n being the numbers 1-5, depending upon the user's selection). Lines 16-18 check that the test method returned a valid result, and assigns that result to the output TextBox, using the setString(...) method. Finally, lines 26-28 change the display back to the list of available tests.

It Takes Two
One other thing is missing from this network conversation - Test- Servlet.java, shown in Listing 7. This is not the right place for a deep and meaningful discussion on servlet development, but to summarize what the servlet is doing:

  1. A doPost event arrives to be handled by the servlet.
  2. The servlet gets the writer for the ServletResponse object.
  3. The content type of the response is set.
  4. The parameter "action" value is retrieved from the ServletRequest.
  5. The parameter is converted to an int, and the word "test" is appended to a string a number of times based upon that number.
  6. Finally, the number is concatenated on the end of the string, and the result is written out using the writer.
Figures 4 and 5 show the MIDlet (and servlet, behind the scenes) in action:

Figure 4
Figure  4:
Figure 5
Figure  5:

Summary
We've now covered the major parts of creating MIDP applications. In Part 3, we'll put all the pieces together extending the PhoneBook application already created. We'll retrieve data using an EJB, and funnel it through a servlet and into our J2ME front end.

Author Bio
Jason Briggs works as a Java analyst programmer in London. He's been officially developing in Java for three-and-a-half years - unofficially for over four. [email protected]

Download Assoicated Source File (~ 28.1 KB ~Zip File Format)

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.