Record Listeners

The RMS API includes the ability to define a record listener object. An object that implements the RecordListener interface can be added as a record listener to a record store, and the interface's notification methods will be called when a record in the store is added, updated, or deleted. The listener is registered using the addRecordListener method on the RecordStore object:

store.addRecordListener(this);

From then until the record store closed, the object referenced by this will be notified when the record store changes. Three separate methods are defined in the RecordListener interface:

public void recordAdded(RecordStore store, int recordId);
public void recordChanged(RecordStore store, int recordId);
public void recordDeleted(RecordStore store, int recordId);

To demonstrate how this works, we can extend RmsMIDlet to add movie records in a background thread.[1] The MIDlet object is registered as a record listener, which is called each time the background thread adds a new movie record.

[1] Palm OS does not natively support threads. However, the CLDC mandates support for "green" threads. Green threads are provided by the Java virtual machine, rather than the underlying OS.

First, a new class represents the thread:

package com.javaonpdas.persistence.threadedrms;

import java.io.*;
import javax.microedition.rms.*;

public class RmsThread extends Thread {

  int count = 0;
  Movie[] movies = null;
  RecordStore store = null;

  public RmsThread(int count, Movie[] movies,
    RecordStore store) {

    this.count = count;
    this.movies = movies;
    this.store = store;
  }

  public void run() {
    try {
      // add the specified number of movies to the record store,
      // sleeping between adds
      for (int i=0; i<count; i++) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        Movie movie = movies[i % movies.length];
        // write the object to the stream
        movie.writeObject(dos);
        byte[] ba = bos.toByteArray();
        store.addRecord(ba, 0, ba.length);
        // now sleep for a while
        sleep(1000);
      }
      store.closeRecordStore();
    }
    catch (Exception e) {
        e.printStackTrace ();
    }
  }
}

This thread class adds a new movie record and sleeps for one second. It repeats this for a specified number of times.

Next, we can define a new Start command that starts the thread:

Command startCommand = new Command("Start", Command.SCREEN, 1);
...
mainForm.addCommand(startCommand);
...
if (c == startCommand) {
  store = RecordStore.openRecordStore(recordStoreName, true);
  store.addRecordListener(this);
  RmsThread thread = new RmsThread(5, movies, store);
  thread.start();
}

Finally, we need to implement the RecordListener methods. In this example, we will retrieve the movie record and display the title of records added and updated. Note that trying to retrieve the record specified in the recordDeleted method will fail, and an InvalidRecordIDException will be thrown:

public void recordAdded(RecordStore store, int recordId) {
  resultItem.setLabel ("Status:");
  resultItem.setText("Movie added: " +
    getMovieFromRecord(store, recordId).title);
}

public void recordChanged(RecordStore store, int recordId) {
  resultItem.setLabel ("Status:");
  resultItem.setText("Movie updated: " +
    getMovieFromRecord(store, recordId).title);
}

public void recordDeleted(RecordStore store, int recordId) {
  resultItem.setLabel ("Status:");
  resultItem.setText("Movie deleted: " + recordId);
}

And the convenience method getMovieFromRecord is defined as follows:

Movie getMovieFromRecord(RecordStore store, int recordId) {
  Movie movie = new Movie();
  try {
    ByteArrayInputStream bis =
      new ByteArrayInputStream(store.getRecord(recordId));
    DataInputStream dis = new DataInputStream(bis);
    movie.readObject(dis);
  }
  catch (Exception e) {
    System.out.println(e);
    e.printStackTrace();
  }
  return movie;
}

Putting it all together, we have the following MIDlet code as follows:

package com.javaonpdas.persistence.threadedrms;

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

public class ThreadedRmsMIDlet extends MIDlet
  implements CommandListener, RecordComparator, RecordFilter,
  RecordListener {

  private Form mainForm = new Form("ThreadedRmsMIDlet");
  private StringItem resultItem = new StringItem("", "");
  private Command startCommand = new Command("Start",
    Command.SCREEN, 1);
  private Command saveCommand = new Command("Save",
    Command.SCREEN, 1);
  private Command getCommand = new Command("Get",
    Command.SCREEN, 1);
  private Command infoCommand = new Command("Info",
    Command.SCREEN, 1);
  private Command deleteCommand = new Command("Delete",
    Command.SCREEN, 1);
  private Command exitCommand = new Command("Exit",
    Command.EXIT, 10);
  private final String recordStoreName = "MyStore";

  private final Movie[] movies = {
    new Movie("The Patriot",
              "Mel Gibson, Heath Ledger, Joely Richardson",
              2000),
    new Movie("Gladiator",
              "Russell Crowe, Joaqin Phoenix, Connie Nielsen",
              2000),
    new Movie("Swordfish",
              "John Travolta, Hugh Jackman",
              2001),
    new Movie("Final Fantasy",
              "Alec Baldwin, Steve Buscemi",
              2001),
    new Movie("The Terminator",
              "Arnold Schwarzenegger, Michael Biehn",
              1984),
    new Movie("The Matrix",
              "Keanu Reeves, Laurence Fishburne",
              1999)};

  public ThreadedRmsMIDlet() {
    mainForm.append(resultItem);
    mainForm.addCommand(startCommand);
    mainForm.addCommand(saveCommand);
    mainForm.addCommand(getCommand);
    mainForm.addCommand(infoCommand);
    mainForm.addCommand(deleteCommand);
    mainForm.addCommand(exitCommand);
    mainForm.setCommandListener(this);
  }

  public void startApp() {
    Display.getDisplay (this).setCurrent (mainForm);
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

  public void commandAction(Command c, Displayable d) {
    RecordStore store = null;
    try {
      if (c == startCommand) {
        store = RecordStore.openRecordStore(recordStoreName,
          true);
        store.addRecordListener(this);
        RmsThread thread = new RmsThread(5, movies, store);
        thread.start();
      }
      if (c == saveCommand) {
        store = RecordStore.openRecordStore(recordStoreName,
          true);
        for (int i=0; i<movies.length; i++) {
          ByteArrayOutputStream bos =
            new ByteArrayOutputStream();
          DataOutputStream dos = new DataOutputStream(bos);
          // write the object to the stream
          movies[i].writeObject(dos);
          byte[] ba = bos.toByteArray();
          store.addRecord(ba, 0, ba.length);
        }
        store.closeRecordStore();
        resultItem.setLabel ("Status:");
        resultItem.setText(movies.length + " records written");
      }
      else if (c == getCommand) {
        store = RecordStore.openRecordStore(recordStoreName,
          false);
        StringBuffer result = new StringBuffer();
        RecordEnumeration re = store.enumerateRecords(this,
          this, false);
        // RecordEnumeration re = store.enumerateRecords(null,
        //   this, false);
        int i=1;
        while(re.hasNextElement()) {
          ByteArrayInputStream bis = new ByteArrayInputStream(
            re.nextRecord());
          DataInputStream dis = new DataInputStream(bis);
          Movie movie = new Movie();
          movie.readObject(dis);
          result.append(i++ + ": " + movie.title + '\n');
        }
        store.closeRecordStore();
        resultItem.setLabel ("Status:");
        resultItem.setText(result.toString());
      }
      else if (c == infoCommand) {
        store = RecordStore.openRecordStore(recordStoreName,
          false);
        StringBuffer result = new StringBuffer();
        result.append("Name: " + store.getName() + "\n");
        result.append("Records: " + store.getNumRecords() +
          '\n');
        result.append("Store size: " + store.getSize() +
          " bytes\n");
        result.append("Bytes available: " +
          store.getSizeAvailable() + " bytes\n");
        result.append("Version: " + store.getVersion() + "\n");
        resultItem.setLabel("Info:");
        resultItem.setText(result.toString());
      }
      else if (c == deleteCommand) {
        RecordStore.deleteRecordStore(recordStoreName);
        resultItem.setLabel ("Status:");
        resultItem.setText(recordStoreName + " deleted.");
      }
      else if (c == exitCommand) {
        destroyApp(false);
        notifyDestroyed();
      }
    }
    catch (Exception e) {
      e.printStackTrace ();
      resultItem.setLabel ("Error:");
      resultItem.setText (e.toString ());
    }
  }

  public int compare(byte[] rec1, byte[] rec2)
  {
    Movie movie1 = null;
    Movie movie2 = null;
    try {
      ByteArrayInputStream bis1 =
        new ByteArrayInputStream(rec1);
      DataInputStream dis1 = new DataInputStream(bis1);
      movie1 = new Movie();
      movie1.readObject(dis1);
      ByteArrayInputStream bis2 =
        new ByteArrayInputStream(rec2);
      DataInputStream dis2 = new DataInputStream(bis2);
      movie2 = new Movie();
      movie2.readObject(dis2);
    }
    catch (Exception e) {
      System.out.println(e);
      e.printStackTrace();
    }

    // sort by title
    int result = movie1.title.compareTo(movie2.title);
    if (result < 0) {
      return RecordComparator.PRECEDES;
    }
    else if (result > 0) {
      return RecordComparator.FOLLOWS;
    }
    else {
      return RecordComparator.EQUIVALENT;
    }
  }

  public boolean matches(byte[] candidate) {
    boolean result = true;
    Movie movie = null;
    try {
      ByteArrayInputStream bis =
        new ByteArrayInputStream(candidate);
      DataInputStream dis = new DataInputStream(bis);
      movie = new Movie();
      movie.readObject(dis);
    }
    catch (Exception e) {
      System.out.println(e);
      e.printStackTrace();
    }
    result = movie.title.startsWith("The");
    return result;
  }

  Movie getMovieFromRecord(RecordStore store, int recordId) {
    Movie movie = new Movie();
    try {
      ByteArrayInputStream bis =
        new ByteArrayInputStream(store.getRecord(recordId));
      DataInputStream dis = new DataInputStream(bis);
      movie.readObject(dis);
    }
    catch (Exception e) {
      System.out.println(e);
      e.printStackTrace();
    }
    return movie;
  }

  public void recordAdded(RecordStore store, int recordId) {
    resultItem.setLabel ("Status:");
    resultItem.setText("Movie added: " +
      getMovieFromRecord(store, recordId).title);
  }

  public void recordChanged(RecordStore store, int recordId) {
    resultItem.setLabel ("Status:");
    resultItem.setText("Movie updated: " +
      getMovieFromRecord(store, recordId).title);
  }

  public void recordDeleted(RecordStore store, int recordId) {
    resultItem.setLabel ("Status:");
    resultItem.setText("Movie deleted: " + recordId);
  }
}

When the MIDlet is executed and the Start button pressed, a movie's title is displayed as it is added to the record store, as shown in Figure 6.4.

Figure 6.4. ThreadedRmsMIDlet

graphics/06fig04.gif