MIDP Database Programming Using RMS: a Persistent Storage for MIDlets

   

storage Persistent storage is a non-volatile place for storing the state of objects. For some applications, you might need objects to exist even after the application that created those objects quits. Without persistent storage, objects and their states are destroyed when an application closes. If you save objects to persistent storage, their lifetime is longer than the program that created them, and later you can read their state and continue to work with them.

The persistent storage facilities provided in the Java 2 Standard Edition (J2SE) platform, such as the JDBC and Object Serialization APIs, are not suitable for handheld devices with a small memory footprint. This is because the storage requirements vary significantly from one resource-constrained device to another. For example, a MIDlet that lets you buy (add to your portfolio) and sell (delete from your portfolio) stocks through your cell phone needs a place to store the stock database.

This article introduces the details of the MIDP Record Management System (RMS), a persistent storage for MIDlets, and shows how to develop MIDP database applications, using a stock database example. Throughout this article the terms record store and database are used interchangeably.

Introducing the RMS

The MIDP provides a mechanism for MIDlets to persistently store data and retrieve it later. This mechanism is a simple record-oriented database called the Record Management System (RMS). A MIDP database (or a record store) consists of a collection of records that remain persistent after the MIDlet exits. When you invoke the MIDlet again, it can retrieve data from the persistent record store.

To use the RMS, import the javax.microedition.rms package.

Introducing the Record Store

Record stores (binary files) are platform-dependent because they are created in platform-dependent locations. MIDlets within a single application (a MIDlet suite) can create multiple record stores (database files) with different names. The RMS APIs provide the following functionality:

  • Allow MIDlets to manipulate (add and remove) records within a record store.
  • Allow MIDlets in the same application to share records (access one another's record store directly).
  • Do not provide a mechanism for sharing records between MIDlets in different applications.

Record Store Names

Record store names are case sensitive, and cannot be more than 32 characters long. Also, a MIDlet cannot create two record stores with the same name in the same application, but it can create a record store with the same name as a MIDlet in another application. When you create a new record store, it is stored under a directory called NOJAM. For example, assume you are using the Wireless Toolkit and that it is is installed under C:\J2MEWTK. If your project name is StockQuotes and your record store is mystocks, the record store is created under C:\J2MEWTK\NOJAM and it has the name mystocks.db.

Working with Threads

The MIDP RMS implementation ensures that all individual record store operations are atomic, synchronous, and serialized, so no corruption occurs with multiple access. However, if your MIDlets use multiple threads to access a record store, it is your responsibility to synchronize this access, or some of your records might be overwritten.

The RMS Package

The RMS package consists of the following four interfaces, one class, and five exception classes:

Interfaces

  • RecordComparator: Defines a comparator to compare two records.
  • RecordEnumeration: Represents a bidirectional record enumerator.
  • RecordFilter: Defines a filter to examine a record and checks if it matches based on a criteria defined by the application.
  • RecordListener: Receives records which were added, changed, or deleted from a record store.

Classes

  • RecordStore: Represents a record store.

 

Exceptions

  • InvalidRecordIDException: Thrown to indicate the RecordID is invalid.
  • RecordStoreException: Thrown to indicate a general exception was thrown.
  • RecordStoreFullException: Thrown to indicate the record store file system is full.
  • RecordStoreNotFoundException: Thrown to indicate the record store could not be found.
  • RecordStoreNotOpenException: Thrown to indicate an operation on a closed record store.

Programming with the RMS

Database programming with RMS is relatively straightforward. This section covers the essential RecordStore methods, and if you want to learn about its other methods, see the javax.microedition.rms APIs.

What is a Record Store?

A record store consists of a collection of records that are uniquely identified by their record ID, which is an integer value. The record ID is the primary key for the records. The first record has an ID of 1, and each additional record is assigned an ID that is the previous value plus 1.

Opening a Record Store

To open a record store, use the openRecordStore() static method:

RecordStore db = RecordStore.openRecordStore("myDBfile", true);

The above code creates a new database file named myDBfile. The second parameter, which is set to true, says that if the record store does not exist, create it.

Note: If the openRecordStore() method is called by a MIDlet when the record store is already open by another MIDlet in the same application, the method returns a reference to the same RecordStore object..

Creating a New Record

A record is an array of bytes. You can use the DataInputStream, DataOutputStream, ByteArrayInputStream, and ByteArrayOutputStream classes to pack and unpack data types into and out of the byte arrays. The first record created has an ID of 1 and is the primary key. The second record has the previous ID + 1.

Now suppose you have the following string record: FirstName, LastName, Age. To add this record to the record store, use the addRecord() method as follows:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeUTF(record);
byte[] b = baos.toByteArray();
db.addRecord(b, 0, b.length);

You construct a DataOutputStream for writing the record to the record store, then you convert the ByteArrayOutputStream to a byte array, and finally you invoke addRecord() to add the record to the record store. Note that in this segment of code, no exceptions are handled. The stock database example discussed later shows how to handle the exceptions.

Reading Data from the Record Store

To read a record from the record store, you construct input streams instead of output streams. This is done as follows:

ByteArrayInputStream bais = new
ByteArrayInputStream(record1);
DataInputStream dis = new
DataInputStream(bais);
String in = dis.readUTF();

Deleting a Record from the Record Store

To delete a record from the record store, you have to know the record ID for the record to be deleted. To delete the record, use the deleteRecord() method. This method takes an integer as a parameter, which is the record ID of the record to be deleted.

There is no method to get the record ID. To work around this, every time you create a new record, add its record ID to a vector like this:

Vector recordIDs = new Vector();
int lastID = 1;
//Add a record....parameters are missing here
db.addRecord();
// Now add the ID to the vector
recordIDs.addElement(new Integer(++lastID));

Now, to delete a record, find the record ID of the record you want to delete:

Enumeration IDs = recordIDs.elements();
while(IDs.hasMoreElements()) {
    int id = ((Integer) IDs.nextElement()).intValue();
    //Compare to see if this is the record you want by
    //invoking compare() which is shown next.
    //Then call db.deleteRecord(id);
}

Comparing my Record with Records in the Record Store

To search for the right record to delete, your application must implement the Comparator interface (by providing an implementation to the compare method) to compare two records. The return value indicates the ordering of the two records. For example, suppose you want to compare two strings that you retrieved from two records. Here is a sample implementation:

public someClas implements Comparator {
  public int compare(byte[] record1, 
                    byte[] record2) {
    ByteArrayInputStream bais1 = new 
                ByteArrayInputStream(record1);
    DataInputStream dis1 = new 
                DataInputStream(bais1);
    ByteArrayInputStream bais2 = new 
                ByteArrayInputStream(record2);
    DataInputStream dis2 = new 
                DataInputStream(bais2);

    String name1 = dis1.readUTF();
    String name2 = dis.readUTF();
    int num = name1.compareTo(name2);
    if (num > 0) {
      return RecordComparator.FOLLOWS;
    } else if (num < 0) {
      return recordcomparator.precedes;
    } else {
      return recordcomparator.equivalent;
    }
  }
}

The constants FOLLOWS, PRECEDES, and EQUIVALENT are defined in the RecordComparator interface and have the following meanings:

  • FOLLOWS: Its value is 1 and means the left parameter follows the right parameter in terms of search or sort order.
  • PRECEDES: Its value is -1 and means the left parameter precedes the right parameter in terms on search or sort order.
  • EQUIVALENT: Its value is 0 and means the two parameters are the same.

Closing the Record Store

To close the record store, use the closeRecordStore() method.

Example: Building a Stock Database

This example demonstrates how to work with the RMS to build a real MIDlet application. This application also builds on previous experience you have gained from the MIDP Network Programming article. This application is similar to the StockMIDlet demo that comes with the MIDP.

The MIDlet for this example does the following:

  • Creates a record store (database).
  • Adds new records (stocks) to the database.
  • Views the stocks in the database.

To add a stock to the database, the user enters the stock symbol (such as, SUNW, IBM, IT, MS, GM, or Ford). The MIDlet retrieves the corresponding stock quote from the Yahoo Quote Server (http://quote.yahoo.com), constructs a record, and adds the record to the database.

To view the stocks in the record store, the MIDlet iterates through the records in the record store and prints them on the display in a nice format.

The Implementation

The implementation of this MIDlet consists of the following three classes: Stock.java, StockDB.java, and QuotesMIDlet.java.

The Stock.java Class

This class parses a string obtained from the Yahoo Quote Server or the record store into fields (such as name of stock or price). The string returned from the Yahoo Quote Server has the following format:

NAME TIME PRICE CHANGE LOW HIGH OPEN PREV
"SUNW", "2:1PM - 79.75", +3.6875, "64.1875 - 129.3125", 78, 76.0625

In this MIDlet, the fields retrieved are the name of the stock, the time, and the price.

Listing 1: Stock.java

public class Stock {
   private static String name, time, price;
   // Given a quote from the server, 
   // retrieve the name, 
   //price, and date of the stock       
   public static void parse(String data) {
     int index = data.indexOf('"');    
     name = data.substring(++index, (index = data.indexOf('"', index)));
     index +=3;
     time = data.substring(index, (index = data.indexOf('-', index))-1);
     index +=5;
     price = data.substring(index, (index = data.indexOf('<', index)));
   }
  // get the name of the stock from 
  // the record store
   public static String getName(String record) {
     parse(record);
     return(name);
   }
  // get the price of the stock from 
  // the record store  
   public static String getPrice(String record) {
     parse(record);
     return(price);
   }

}

To

The StockDB.java Class

This class provides methods that do the following:

  • Opens a new record store
  • Adds a new record to the record store
  • Closes the record store
  • Enumerates through the records

Once you understand how to open a record store, add a new record, and close the record store, this code is easy to follow.

Listing 2: StockDB.java

import javax.microedition.rms.*;
import java.util.Enumeration;
import java.util.Vector;
import java.io.*;

public class StockDB {
   RecordStore recordStore = null;
   public StockDB() {}

   // Open a record store with the given name
   public StockDB(String fileName) {
      try {
        recordStore = 
              RecordStore.openRecordStore(
                           fileName, true);
      } catch(RecordStoreException rse) {
        rse.printStackTrace();
      }
   }

   // Close the record store
   public void close() 
            throws RecordStoreNotOpenException,
                       RecordStoreException {
        if (recordStore.getNumRecords() == 0) {
            String fileName = 
                           recordStore.getName();
            recordStore.closeRecordStore();
            recordStore.deleteRecordStore(
                                      fileName);
        } else {
            recordStore.closeRecordStore();
        }
    }

   // Add a new record (stock) 
   // to the record store
   public synchronized void 
             addNewStock(String record) {
        ByteArrayOutputStream baos = new 
                ByteArrayOutputStream();
        DataOutputStream outputStream = new 
                DataOutputStream(baos);
        try {
            outputStream.writeUTF(record);
        }
        catch (IOException ioe) {
            System.out.println(ioe);
            ioe.printStackTrace();
        }
        byte[] b = baos.toByteArray();
        try {
            recordStore.addRecord(b, 
                             0, b.length);
        }
        catch (RecordStoreException rse) {
            System.out.println(rse);
            rse.printStackTrace();
        }
    }

    // Enumerate through the records.
    public synchronized 
                 RecordEnumeration enumerate() 
          throws RecordStoreNotOpenException {
       return recordStore.enumerateRecords(
                null, null, false);
    }
}

The QuotesMIDlet.java Class

The QuotesMIDlet class is the actual MIDlet that does the following:

  • Creates commands (List Stocks, Add New Stock, Back, Save, Exit)
  • Handles command events
  • Connects to the YAHOO Quote Server and retrieves Quotes
  • Invokes methods from Stock and StockDB to parse quotes and add new stocks to the record store

Listing 3: QuotesMIDlet.java

import javax.microedition.rms.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.io.*;
import java.io.*;
import java.util.Vector;

public class QuotesMIDlet 
        extends MIDlet implements CommandListener {
    Display display = null;
    List menu = null; // main menu
    List choose = null;
    TextBox input = null;
    Ticker ticker = 
                new Ticker("Database Application");
    String quoteServer = 
        "http://quote.yahoo.com/d/quotes.csv?s=";
    String quoteFormat = 
        "&f=slc1wop"; // The only quote format supported

    static final Command backCommand = new 
                Command("Back", Command.BACK, 0);
    static final Command mainMenuCommand = new 
                Command("Main", Command.SCREEN, 1);
    static final Command saveCommand = new 
                Command("Save", Command.OK, 2);
    static final Command exitCommand = new 
                Command("Exit", Command.STOP, 3);
    String currentMenu = null;

    // Stock data
    String name, date, price;
    
    // record store
    StockDB db = null;

    public QuotesMIDlet() { // constructor
    }

    // start the MIDlet
    public void startApp() 
        throws MIDletStateChangeException {
      display = Display.getDisplay(this);
      // open a db stock file
      try {
        db = new StockDB("mystocks");
      } catch(Exception e) {}
      menu = new List("Stocks Database", 
                          Choice.IMPLICIT);
      menu.append("List Stocks", null);
      menu.append("Add A New Stock", null);
      menu.addCommand(exitCommand);
      menu.setCommandListener(this);
      menu.setTicker(ticker);

      mainMenu();
    }

    public void pauseApp() {
      display = null;
      choose = null;
      menu = null;
      ticker = null;
       
      try {
        db.close();
        db = null;
      } catch(Exception e) {}
    }

    public void destroyApp(boolean 
                        unconditional) {
      try {
        db.close();
      } catch(Exception e) {}
      notifyDestroyed();
    }

    void mainMenu() {
      display.setCurrent(menu);
      currentMenu = "Main"; 
    }

    // Construct a running ticker 
    // with stock names and prices
    public String tickerString() {
       StringBuffer ticks = null;
       try {
          RecordEnumeration enum = 
                           db.enumerate();
          ticks = new StringBuffer();
          while(enum.hasNextElement()) {
            String stock1 = 
                new String(enum.nextRecord());
            ticks.append(Stock.getName(stock1));
            ticks.append(" @ ");
            ticks.append(Stock.getPrice(stock1));
            ticks.append("    ");
          }
       } catch(Exception ex) {}
       return (ticks.toString());
    }

    // Add a new stock to the record store 
    // by calling StockDB.addNewStock()
    public void addStock() {
      input = new TextBox(
                "Enter a Stock Name:", "", 5, 
                TextField.ANY);
      input.setTicker(ticker);
      input.addCommand(saveCommand);
      input.addCommand(backCommand);
      input.setCommandListener(this);
      input.setString("");
      display.setCurrent(input);
      currentMenu = "Add";
    }

    // Connect to quote.yahoo.com and 
    // retrieve the data for a given 
    // stock symbol.
    public String getQuote(String input) 
                throws IOException, 
                      NumberFormatException {
      String url = quoteServer + input + 
                                 quoteFormat;
      StreamConnection c = 
           (StreamConnection)Connector.open(
                url, Connector.READ_WRITE);
      InputStream is = c.openInputStream();
      StringBuffer sb = new StringBuffer();
      int ch;
      while((ch = is.read()) != -1) {
        sb.append((char)ch);
      }
      return(sb.toString());
    }
   
    // List the stocks in the record store
    public void listStocks() {
        choose = new List("Choose Stocks", 
                        Choice.MULTIPLE);
        choose.setTicker(
             new Ticker(tickerString()));
        choose.addCommand(backCommand);
        choose.setCommandListener(this);
      try {
         RecordEnumeration re = db.enumerate();
         while(re.hasNextElement()) {
           String theStock = 
                   new String(re.nextRecord());
           choose.append(Stock.getName(
                                theStock)+" @ " 
            + Stock.getPrice(theStock), null);
         }
      } catch(Exception ex) {}
      display.setCurrent(choose);
      currentMenu = "List"; 
   }
 
   // Handle command events 
   public void commandAction(Command c, 
                        Displayable d) {
      String label = c.getLabel();
      if (label.equals("Exit")) {
         destroyApp(true);
      } else if (label.equals("Save")) {
          if(currentMenu.equals("Add")) {
              // add it to database
            try {
              String userInput = 
                           input.getString();
              String pr = getQuote(userInput);
              db.addNewStock(pr);
              ticker.setString(tickerString()); 
            } catch(IOException e) {
            } catch(NumberFormatException se) {
            }
            mainMenu();
          } 
      } else if (label.equals("Back")) {
          if(currentMenu.equals("List")) {
            // go back to menu
            mainMenu();
          } else if(currentMenu.equals("Add")) {
            // go back to menu
            mainMenu();
          }
      } else {
         List down = (List)display.getCurrent();
         switch(down.getSelectedIndex()) {
           case 0: listStocks();break;
           case 1: addStock();break;
         }
      }
  }
}

Testing QuotesMIDlet

To test QuotesMIDlet, which was developed using the Wireless Toolkit:

  1. Create a new project and compile the code.
  2. Run the MIDlet in the emulator.
    You see QuotesMIDlet running in the emulator as shown in Figure 1:

    QuotesMIDLET
    Figure 1: QuotesMIDlet

    For more information about testing MIDlets using the Wireless Toolkit, see the Quick Start to the Wireless Toolkit

  3. Activate QuotesMIDlet.

    You see a menu with the following two options: List Stocks and Add a New Stock, as shown in Figure 2:

    Menus
    Figure 2: QuotesMIDlet Stock Database

    Choose the Add A New Stock option and add a few stocks.

    In this example, the stocks IBM, GM, and NOR were added, as shown in Figure 3:

    Add Stock Add Stock Add Stock
    Figure 3: Adding new stocks

  4. Go back and choose the View Stocks option. This option reads the record store and retrieves all the records (stocks) that have been added as shown in Figure 4.

    Add Stock
    Figure 4: Viewing the record store

Going Forward

The stock quotes example demonstrates how to create new records and view the records in the database. As an exercise, consider modifying QuoteMIDlet to handle the following situations:

  • If a new stock is being added and it is already in the record store then modify the code to update the record (update the price info, etc).
  • Add functionality to remove records from the database chosen by the user. For example, when the user selects stocks to be deleted, delete them.
  • If the user enters a stock symbol that does not exist on the quote server, then handle that condition.

More Information

MIDP Network Programming using HTTP and the Connection Framework

Quick Guide to the J2ME Wireless Toolkit

Java 2 Platform, Micro Edition (J2ME)



Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.

Back To Top