Archive for the ‘persistent storage’ Category

More about JSR 75

For today’s JSR adventure, I improved my SMS checkers game — as promised — by adding a feature to read contacts from the handset address book to choose opponents. The new version is available as CheckersPlus. Then I figured that since I’m playing with JSR 75, I should write a sample program to test the other half of JSR 75: the FileConnection API.

I like the elegance of the FileConnection API, and in particular the way it’s designed to function just like the various network connection APIs (using Connector.open with a URL, returning a Connection with an InputStream and and OutputStream just as any a network connection). It’s a very natural way of accessing files both from the Java perspective (where files have traditionally been accessed through the same streams as sockets, etc.) and in the general networking universe (where users are already accustomed to reading local files in an Internet browser using a file://-type url).

Another cool advantage to FileConnection is that you can access parts of the handset’s memory that you can’t touch from MIDP’s standard little RMS memory sandbox. I found by listing the available file roots on my Sagem my700x that I can access the images folder. So for some handsets it may be possible to have a Java MIDlet store a wallpaper or a ringtone as a wallpaper or a ringtone by storing it in the right place where the handset can use it.

But that brings us to the usual disadvantage: you have to know the particular handset very well to know which folders you have access to and how to access them. So it’s great if your project has a small set of target handsets, and not so great if you want your application to be useful on all MIDP handsets across the board. The RMS (Record Management System) has the advantage of always being supported, so it’s probably better to use the RMS if all you want is to set some simple data aside and find it again later.

Of course, even the RMS isn’t as predictible as one might like. There are handsets where RMS access is slower than FileConnection access (and some where it’s not), some that handle individual records in unexpected ways (setting aside a big space for each record even if it’s not used, or failing to free up the memory of a deleted record). Then there’s the fact that the RMS is not convenient for pooling data among multiple MIDlet suites: To share a record store between suites, you need to leave the access door open to every MIDlet on the device, plus the record store is deleted when the associated MIDlet suite is deleted even if other MIDlet suites still wanted to access the data.

So, there’s a bit of a trade-off, meaning it’s better to know both the FileConnection API as well as the RMS, and choose which one to use depending on your application.

For today’s fun, here’s a sample class to illustrate the FileConnection API, doing a few very basic, standard things like listing the available roots and files; reading, writing, creating a test file, etc. As with PIM, FileConnection access is done in its own thread:

package net.frog_parrot.test;

import java.util.Enumeration;
import java.io.*;

import javax.microedition.io.*;
import javax.microedition.io.file.*;

/**
* A simple file Connection testing utility.
*/
public class FCRunner extends Thread {

FCTest myMidlet;

FCRunner(FCTest test) {
myMidlet = test;
}

public void run() {
FileConnection rootdir = null;
try {
Enumeration items = FileSystemRegistry.listRoots();

// Now print the available roots:
while(items.hasMoreElements()) {
String rootname = (String)(items.nextElement());
myMidlet.display(“\n *** new root: ” + rootname);

// open the root directory:
// note there are three slashes before the root name
// because there is no “host” for this connection:
rootdir = (FileConnection)Connector.open(
“file:///” + rootname);
// List the current files:
Enumeration ls = rootdir.list();
while(ls.hasMoreElements()) {
String filename = (String)(ls.nextElement());
myMidlet.display(” file: ” + filename);

// print the contents of the file:
FileConnection file = null;
try {
file = (FileConnection)Connector.open(
“file:///” + rootname + “/” + filename);
if(file.canRead()) {
InputStream is = file.openInputStream();
byte[] contents = new byte[25];
int len = is.read(contents);
is.close();
myMidlet.display(” contents: ”
+ new String(contents, 0, len));
} else {
myMidlet.display(” * not readable”);
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
file.close();
} catch(Exception e) {}
}
}

// now try to create a file:
FileConnection newfile = null;
try {
newfile = (FileConnection)Connector.open(
“file:///” + rootname + “myNewFile”);
if(newfile.exists()) {
OutputStream os = newfile.openOutputStream();
os.write((new String(“overwriting old contents”)).getBytes());
os.close();
} else {
newfile.create();
OutputStream os = newfile.openOutputStream();
os.write((new String(“creating new contents”)).getBytes());
os.close();
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
newfile.close();
} catch(Exception e) {}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
rootdir.close();
} catch(Exception e) {}
}
}

}

For completeness, here’s the MIDlet class to use to run the above FileConnection-testing class.  These two classes together make a complete MIDlet that can be easily built by creating a new project for it in the WTK:

package net.frog_parrot.test;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.MIDlet;

/**
* A simple MIDlet to test which APIs are implemented.
*/
public class FCTest extends MIDlet implements CommandListener {

private Command myExitCommand = new Command(“Exit”, Command.EXIT, 1);
private Command myOkCommand = new Command(“OK”, Command.OK, 1);
private Form myResultScreen;

/**
* Empty constructor.
*/
public FCTest() {
}

/**
* Initialize the Displayables.
*/
public void startApp() {
myResultScreen = new Form(“Results”);
myResultScreen.addCommand(myExitCommand);
myResultScreen.addCommand(myOkCommand);
myResultScreen.setCommandListener(this);
Display.getDisplay(this).setCurrent(myResultScreen);
}

/**
* Implementation of MIDlet.
*/
public void pauseApp() {
}

/**
* Implementation of MIDlet.
*/
public void destroyApp(boolean unconditional) {
}

/**
* Respond to a button push.
*/
public void commandAction(Command command, Displayable screen) {
if(command == myExitCommand) {
destroyApp(true);
notifyDestroyed();
} else {
FCRunner runner = new FCRunner(this);
runner.start();
}
}

/**
* Append a string to the current display.
*/
public void display(String str) {
myResultScreen.append(str);
myResultScreen.append(“\n”);
}

}

Advertisements

A simple PIM example

Now that I’ve got my SMS Checkers game up and running, I’d like to play it against a friend. But I don’t want to have to type my friend’s mobile number into that little phone number text field every time — really I don’t even know his number off the top of my head since I always just call people by selecting numbers from my address book. What to do?

This looks like a job for JSR 75: PIM!!!

JSR 75 also covers the File Connection API, but for today let’s talk about the Personal Information Management API.

Browsing through the JavaDoc, it seems pretty clear that this API is one where they’ve opted for flexibility over simplicity. The addressbooks, calendars, and to-do lists on the various handsets each have a fixed structure, however there’s no reason to expect all of these personal information datastores to have the same structure as each other. So the PIM API was designed to allow you to find the fields and records you need regardless of the underlying structrure of the datastore.

It’s actually a little confusing to try to figure out from the JavaDoc how to get the information you want. Fortunately, there aren’t too many classes. The classes to focus on are PIMItem and PIMList, which essentially describe records and record stores respectively. Between these two classes (and subclasses such as Contact), you’ll find the methods you need to determine what fields (such as TEL) a record has data for, which attributes (such as HOME or MOBILE) are associated with the field, and what type of data the (such as String or int) is contained in the field.

Confused yet?

My first instinct in such a case is to just write a simple example program.

It took a few tries, so I was a little disappointed that there was no clear() method to delete the whole ContactList so I could start over. Yet, it’s probably wise of the API designers to have left that off considering that in real life if your program deletes the user’s entire address book — even if he gave permission to modify it — this would lead to a “bad user experience” in every case. So it’s better not to even have the method there to tempt you to deliberately write some malicious code, which is a no-no in Java ;-).

The solution I found (for the WTK) is just to delete all the files in the following directory whenever I wanted to start over with a clean slate: WTK2.2/appdb/DefaultColorPhone/pim/contacts/Contacts/

The following class creates a couple of very simple records then iterates through the resulting record store printing out exactly what kind of data structure was created. Note that this example isn’t something you’d put in a real application (especially given all the System.out.println() statements). Also note that I made it a subclass of Thread. Accessing the PIM system is a potentially blocking operation that should not be done from any method that needs to return immediately such as startApp() or commandAction().

Here’s the code:

package net.frog_parrot.test;

import java.util.Enumeration;

import javax.microedition.pim.*;

/**
* A simple PIM testing utility.
*/
public class PIMRunner extends Thread {

  public void run() {
      try {
        ContactList addressbook
            = (ContactList)(PIM.getInstance().openPIMList(
            PIM.CONTACT_LIST, PIM.READ_WRITE));
        Contact contact = null;

        // Each PIMItem -- new or found -- is associated with
        // a particular PIMList.
        contact = addressbook.createContact();
        if(addressbook.isSupportedField(Contact.FORMATTED_NAME)) {
          contact.addString(Contact.FORMATTED_NAME, Contact.ATTR_NONE,
             "Lynn Hanson");
        }
        if(addressbook.isSupportedField(Contact.TEL)) {
          contact.addString(Contact.TEL, Contact.ATTR_HOME,
             "555-HOME-NUMBER");
          contact.addString(Contact.TEL, Contact.ATTR_MOBILE,
                            "555-MOBILE-NUMBER");
        }
        // Here's a quick search to see if this contact
        // is already present in the addressbook:
        Enumeration matching = addressbook.items(contact);
        if(matching.hasMoreElements()) {
          System.out.println("found the first contact");
        } else {
          System.out.println("adding the first contact");
          contact.commit();
        }
      
        // Let's create one more contact:
        contact = addressbook.createContact();
        if(addressbook.isSupportedField(Contact.FORMATTED_NAME)) {
          contact.addString(Contact.FORMATTED_NAME, Contact.ATTR_NONE,
             "Spencer Hobbs");
        }
        if(addressbook.isSupportedField(Contact.TEL)) {
          contact.addString(Contact.TEL, Contact.ATTR_HOME,
             "5555-HOME-NUMBER");
          contact.addString(Contact.TEL, Contact.ATTR_MOBILE,
             "5555-MOBILE-NUMBER");
        }
        matching = addressbook.items(contact);
        if(matching.hasMoreElements()) {
          System.out.println("found the second contact");
        } else {
          System.out.println("adding the second contact");
          contact.commit();
        }
            
        // Now print the contents of the addressbook:
        Enumeration items = addressbook.items();
        while(items.hasMoreElements()) {
          System.out.println("\n *** new item ***");
          contact = (Contact)(items.nextElement());
          int[] fields = contact.getFields();
          for(int i = 0; i < fields.length; i++) {
            int fieldIndex = fields[i];
            System.out.println(" field " + fieldIndex + ": "
                + addressbook.getFieldLabel(fieldIndex));
            int dataType = addressbook.getFieldDataType(fieldIndex);
            System.out.println(" * data type: " + dataType);
            if(dataType == PIMItem.STRING) {
              for(int j = 0; j < contact.countValues(fieldIndex); j++) {
                int attr = contact.getAttributes(fieldIndex, j);
                System.out.print(" " + j + ". (");
                System.out.print(addressbook.getAttributeLabel(attr) + "): ");
                System.out.println(contact.getString(fieldIndex, j));
              }
            }
          }
        }
      } catch(Exception e) {
        e.printStackTrace();
      }
  }
  
}

And here’s what it printed out:

adding the first contact
adding the second contact

*** new item ***
field 105: Formatted Name
* data type: 4
0. (None): Lynn Hanson
field 114: Revision
* data type: 2
field 115: Tel.
* data type: 4
0. (Home): 555-HOME-NUMBER
1. (Mobile): 555-MOBILE-NUMBER
field 117: UID
* data type: 4
0. (None): 1.vcf

*** new item ***
field 105: Formatted Name
* data type: 4
0. (None): Spencer Hobbs
field 114: Revision
* data type: 2
field 115: Tel.
* data type: 4
0. (Home): 5555-HOME-NUMBER
1. (Mobile): 5555-MOBILE-NUMBER
field 117: UID
* data type: 4
0. (None): 2.vcf