This Midlet implements a simple addressbook.

 

   
   
/*
 * Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
 */

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.ChoiceGroup;
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.lcdui.Item;
import javax.microedition.lcdui.ItemStateListener;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.Screen;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.rms.RecordComparator;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordFilter;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;

/**
 * This MIDlet implements a simple address book with the following
 * functionality: browsing, entry, deletion, and searching (both on device and
 * over the network).
 */
public class AddressBookMIDlet extends MIDlet implements CommandListener,
    ItemStateListener {

  private RecordStore addrBook;

  private static final int FN_LEN = 10;

  private static final int LN_LEN = 20;

  private static final int PN_LEN = 15;

  final private static int ERROR = 0;

  final private static int INFO = 1;

  private Display display;

  private Alert alert;

  private Command cmdAdd;

  private Command cmdBack;

  private Command cmdCancel;

  private Command cmdDial;

  private Command cmdExit;

  private Command cmdSelect;

  private Command cmdSearchNetwork;

  private Command cmdSearchLocal;

  private List mainScr;

  private String[] mainScrChoices = { "Search", "Add New", "Browse",
      "Options" };

  private Form searchScr;

  private TextField s_lastName;

  private TextField s_firstName;

  private Form entryScr;

  private TextField e_lastName;

  private TextField e_firstName;

  private TextField e_phoneNum;

  private List nameScr;

  private Vector phoneNums;

  private Form optionScr;

  private ChoiceGroup sortChoice;

  private TextBox dialScr;

  private int sortOrder = 1;

  /**
   * Public no-argument constructor. Called by the system to instantiate our
   * class. Caches reference to the display, allocate commands, and tries to
   * open the address book.
   */
  public AddressBookMIDlet() {
    display = Display.getDisplay(this);

    cmdAdd = new Command("Add", Command.OK, 1);
    cmdBack = new Command("Back", Command.BACK, 2);
    cmdCancel = new Command("Cancel", Command.BACK, 2);
    cmdDial = new Command("Dial", Command.OK, 1);
    cmdExit = new Command("Exit", Command.EXIT, 2);
    cmdSelect = new Command("Select", Command.OK, 1);
    cmdSearchNetwork = new Command("Network", Command.SCREEN, 4);
    cmdSearchLocal = new Command("Local", Command.SCREEN, 3);

    alert = new Alert("", "", null, AlertType.INFO);
    alert.setTimeout(2000);

    try {
      addrBook = RecordStore.openRecordStore("TheAddressBook", true);
    } catch (RecordStoreException e) {
      addrBook = null;
    }
  }

  /**
   * Called by the system to start our MIDlet. If the open of the address book
   * fails, display an alert and continue.
   *  
   */
  protected void startApp() {
    if (addrBook == null) {
      displayAlert(ERROR, "Could not open address book", null);
    } else {
      genMainScr();
    }
  }

  /**
   * Called by the system to pause our MIDlet. No actions required by our
   * MIDlet.
   */
  protected void pauseApp() {
  }

  /**
   * Called by the system to end our MIDlet. No actions required by our
   * MIDlet.
   */
  protected void destroyApp(boolean unconditional) {
    if (addrBook != null) {
      try {
        addrBook.closeRecordStore();
      } catch (Exception e) {
      }
    }
  }

  /**
   * Display an Alert on the screen
   * 
   * @param type
   *            One of the following: ERROR, INFO
   * @param msg
   *            Message to display
   * @param s
   *            screen to change to after displaying alert. if null, revert to
   *            main screen
   */
  private void displayAlert(int type, String msg, Screen s) {
    alert.setString(msg);

    switch (type) {
    case ERROR:
      alert.setTitle("Error!");
      alert.setType(AlertType.ERROR);
      break;
    case INFO:
      alert.setTitle("Info");
      alert.setType(AlertType.INFO);
      break;
    }
    display.setCurrent(alert, s == null ? display.getCurrent() : s);
  }

  /**
   * Notify the system that we are exiting.
   */
  private void midletExit() {
    destroyApp(false);
    notifyDestroyed();
  }

  /**
   * Create the first screen of our MIDlet. This screen is a list.
   */
  private Screen genMainScr() {
    if (mainScr == null) {
      mainScr = new List("Menu", List.IMPLICIT, mainScrChoices, null);
      mainScr.addCommand(cmdSelect);
      mainScr.addCommand(cmdExit);
      mainScr.setCommandListener(this);
    }
    display.setCurrent(mainScr);
    return mainScr;
  }

  /**
   * Sort order option screen. Allows us to set sort order to either sorting
   * by last name (default), or first name.
   */
  private Screen genOptionScr() {
    if (optionScr == null) {
      optionScr = new Form("Options");
      optionScr.addCommand(cmdBack);
      optionScr.setCommandListener(this);

      sortChoice = new ChoiceGroup("Sort by", Choice.EXCLUSIVE);
      sortChoice.append("First name", null);
      sortChoice.append("Last name", null);
      sortChoice.setSelectedIndex(sortOrder, true);
      optionScr.append(sortChoice);
      optionScr.setItemStateListener(this);
    }
    display.setCurrent(optionScr);
    return optionScr;
  }

  /**
   * Search screen.
   * 
   * Displays two <code>TextField</code>s: one for first name, and one for
   * last name. These are used for searching the address book.
   * 
   * @see AddressBookMIDlet#genNameScr
   */
  private Screen genSearchScr() {
    if (searchScr == null) {
      searchScr = new Form("Search");
      searchScr.addCommand(cmdBack);
      searchScr.addCommand(cmdSearchNetwork);
      searchScr.addCommand(cmdSearchLocal);
      searchScr.setCommandListener(this);
      s_firstName = new TextField("First name:", "", FN_LEN,
          TextField.ANY);
      s_lastName = new TextField("Last name:", "", LN_LEN, TextField.ANY);
      searchScr.append(s_firstName);
      searchScr.append(s_lastName);
    }

    s_firstName.delete(0, s_firstName.size());
    s_lastName.delete(0, s_lastName.size());
    display.setCurrent(searchScr);
    return searchScr;
  }

  /**
   * Name/Phone number entry screen
   * 
   * Displays three <code>TextField</code>s: one for first name, one for
   * last name, and one for phone number. These are used to capture data to
   * add to the address book.
   * 
   * @see AddressBookMIDlet#addEntry
   */
  private Screen genEntryScr() {
    if (entryScr == null) {
      entryScr = new Form("Add new");
      entryScr.addCommand(cmdCancel);
      entryScr.addCommand(cmdAdd);
      entryScr.setCommandListener(this);

      e_firstName = new TextField("First name:", "", FN_LEN,
          TextField.ANY);
      e_lastName = new TextField("Last name:", "", LN_LEN, TextField.ANY);
      e_phoneNum = new TextField("Phone Number", "", PN_LEN,
          TextField.PHONENUMBER);
      entryScr.append(e_firstName);
      entryScr.append(e_lastName);
      entryScr.append(e_phoneNum);
    }

    e_firstName.delete(0, e_firstName.size());
    e_lastName.delete(0, e_lastName.size());
    e_phoneNum.delete(0, e_phoneNum.size());

    display.setCurrent(entryScr);
    return entryScr;
  }

  /**
   * Generates a list of first/last/phone numbers. Can be called as a result
   * of a browse command (genBrowseScr) or a search command (genSearchScr).
   * 
   * title title of this screen (since it can be called from a browse or a
   * search command. f if not null, first name to search on l if not null,
   * last name to search on
   */
  private Screen genNameScr(String title, String f, String l, boolean local) {
    SimpleComparator sc;
    SimpleFilter sf = null;
    RecordEnumeration re;
    phoneNums = null;

    if (local) {
      sc = new SimpleComparator(
          sortOrder == 0 ? SimpleComparator.SORT_BY_FIRST_NAME
              : SimpleComparator.SORT_BY_LAST_NAME);

      if (f != null || l != null) {
        sf = new SimpleFilter(f, l);
      }

      try {
        re = addrBook.enumerateRecords(sf, sc, false);
      } catch (Exception e) {
        displayAlert(ERROR, "Could not create enumeration: " + e, null);
        return null;
      }
    } else {
      re = new NetworkQuery(f, l, sortOrder);
    }

    nameScr = null;
    if (re.hasNextElement()) {
      nameScr = new List(title, List.IMPLICIT);
      nameScr.addCommand(cmdBack);
      nameScr.addCommand(cmdDial);
      nameScr.setCommandListener(this);
      phoneNums = new Vector(6);

      try {
        while (re.hasNextElement()) {
          byte[] b = re.nextRecord();
          String pn = SimpleRecord.getPhoneNum(b);
          nameScr.append(SimpleRecord.getFirstName(b) + " "
              + SimpleRecord.getLastName(b) + " "
              + SimpleRecord.getPhoneNum(b), null);
          phoneNums.addElement(pn);
        }
      } catch (Exception e) {
        displayAlert(ERROR, "Error while building name list: " + e,
            null);
        return null;
      }
      display.setCurrent(nameScr);

    } else {
      displayAlert(INFO, "No names found", null);
    }

    return nameScr;
  }

  /**
   * Generate a screen with which to dial the phone. Note: this may or may not
   * be implemented on a given implementation.
   */
  private void genDialScr() {
    dialScr = new TextBox("Dialing", (String) phoneNums.elementAt(nameScr
        .getSelectedIndex()), PN_LEN, TextField.PHONENUMBER);
    dialScr.addCommand(cmdCancel);
    dialScr.setCommandListener(this);
    display.setCurrent(dialScr);
  }

  /**
   * Add an entry to the address book. Called after the user selects the
   * addCmd while in the genEntryScr screen.
   */
  private void addEntry() {
    String f = e_firstName.getString();
    String l = e_lastName.getString();
    String p = e_phoneNum.getString();

    byte[] b = SimpleRecord.createRecord(f, l, p);
    try {
      addrBook.addRecord(b, 0, b.length);
      displayAlert(INFO, "Record added", mainScr);
    } catch (RecordStoreException rse) {
      displayAlert(ERROR, "Could not add record" + rse, mainScr);
    }
  }

  /***************************************************************************
   * This method implements a state machine that drives the MIDlet from one
   * state (screen) to the next.
   */
  public void commandAction(Command c, Displayable d) {
    if (d == mainScr) {
      // Handle main sceen
      if (c == cmdExit) {
        midletExit(); // exit
      } else if ((c == List.SELECT_COMMAND) || (c == cmdSelect)) {
        switch (mainScr.getSelectedIndex()) {
        case 0:
          // display search screen
          genSearchScr();
          break;
        case 1:
          // display name entry screen
          genEntryScr();
          break;
        case 2:
          // display all names
          genNameScr("Browse", null, null, true);
          break;
        case 3:
          // display option screen
          genOptionScr();
          break;
        default:
          displayAlert(ERROR, "Unexpected index!", mainScr);
        }
      }
    } else if (d == nameScr) {
      // Handle a screen with names displayed, either
      // from a browse or a search
      if (c == cmdBack) {
        // display main screen
        genMainScr();
      } else if (c == cmdDial) {
        // dial the phone screen
        genDialScr();
      }
    } else if (d == entryScr) {
      // Handle the name entry screen
      if (c == cmdCancel) {
        // display main screen
        genMainScr();
      } else if (c == cmdAdd) {
        // display name entry screen
        addEntry();
      }
    } else if (d == optionScr) {
      // Handle the option screen
      if (c == cmdBack) {
        // display main screen
        genMainScr();
      }
    } else if (d == searchScr) {
      // Handle the search screen
      if (c == cmdBack) {
        // display main screen
        genMainScr();
      } else if (c == cmdSearchNetwork || c == cmdSearchLocal) {

        // display search of local addr book
        genNameScr("Search Result", s_firstName.getString(), s_lastName
            .getString(), c == cmdSearchLocal);
      }
    } else if (d == dialScr) {
      if (c == cmdCancel) {
        // display main screen
        genMainScr();
      }
    }
  }

  /**
   * Gets called when the user is viewing the sort options in the optionScr.
   * Takes the new selected index and changes the sort order (how names are
   * displayed from a search or a browse).
   * 
   * item An item list
   */
  public void itemStateChanged(Item item) {
    if (item == sortChoice) {
      sortOrder = sortChoice.getSelectedIndex();
    }
  }
}

/*
 * Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
 */

/*
 * Class to query a network service for address book entries and parse the
 * result. Uses HttpConnection to fetch the entries from a server.
 * 
 * The http request is made using a base url provided by the caller with the
 * query arguments for last name and first name encoded in the query parameters
 * of the URL.
 */

class NetworkQuery implements RecordEnumeration {
  private StringBuffer buffer = new StringBuffer(60);

  private String[] fields = new String[3];

  private String empty = new String();

  private Vector results = new Vector(20);

  private Enumeration resultsEnumeration;

  final static String baseurl = "http://127.0.0.1:8080/Book/netaddr";

  /**
   * Create a RecordEnumeration from the network.
   * 
   * Query a network service for addresses matching the specified criteria.
   * The base URL of the service has the query parameters appended. The
   * request is made and the contents parsed into a Vector which is used as
   * the basis of the RecordEnumeration. lastname the last name to search for
   * firstname the first name to search for sortorder the order in which to
   * sort 1 is by last name, 0 is by first name
   */
  NetworkQuery(String firstname, String lastname, int sortorder) {
    HttpConnection c = null;
    int ch;
    InputStream is = null;
    InputStreamReader reader;
    String url;

    // Format the complete URL to request
    buffer.setLength(0);
    buffer.append(baseurl);
    buffer.append("?last=");
    buffer.append((lastname != null) ? lastname : empty);
    buffer.append("&first=");
    buffer.append((firstname != null) ? firstname : empty);
    buffer.append("&sort=" + sortorder);

    url = buffer.toString();

    // Open the connection to the service
    try {
      c = open(url);
      results.removeAllElements();

      /*
       * Open the InputStream and construct a reader to convert from bytes
       * to chars.
       */
      is = c.openInputStream();
      reader = new InputStreamReader(is);
      while (true) {
        int i = 0;
        fields[0] = empty;
        fields[1] = empty;
        fields[2] = empty;
        do {
          buffer.setLength(0);
          while ((ch = reader.read()) != -1 && (ch != ',')
              && (ch != '\n')) {
            if (ch == '\r') {
              continue;
            }
            buffer.append((char) ch);
          }

          if (ch == -1) {
            throw new EOFException();
          }

          if (buffer.length() > 0) {
            if (i < fields.length) {
              fields[i++] = buffer.toString();
            }
          }
        } while (ch != '\n');

        if (fields[0].length() > 0) {
          results.addElement(SimpleRecord.createRecord(fields[0],
              fields[1], fields[2]));
        }
      }
    } catch (Exception e) {

    } finally {
      try {
        if (is != null) {
          is.close();
        }
        if (c != null) {
          c.close();
        }
      } catch (Exception e) {
      }
    }
    resultsEnumeration = results.elements();
  }

  /**
   * Read the HTTP headers and the data using HttpConnection. Check the
   * response code to ensure successful open.
   * 
   * Connector.open is used to open url and a HttpConnection is returned. The
   * HTTP headers are read and processed. url the URL to open throws
   * IOException for any network related exception
   */
  private HttpConnection open(String url) throws IOException {
    HttpConnection c;
    int status = -1;

    // Open the connection and check for redirects
    while (true) {
      c = (HttpConnection) Connector.open(url);

      // Get the status code,
      // causing the connection to be made
      status = c.getResponseCode();

      if ((status == HttpConnection.HTTP_TEMP_REDIRECT)
          || (status == HttpConnection.HTTP_MOVED_TEMP)
          || (status == HttpConnection.HTTP_MOVED_PERM)) {

        // Get the new location and close the connection
        url = c.getHeaderField("location");
        c.close();
      } else {
        break;
      }
    }

    // Only HTTP_OK (200) means the content is returned.
    if (status != HttpConnection.HTTP_OK) {
      c.close();
      throw new IOException("Response status not OK");
    }
    return c;
  }

  /**
   * Returns true if more elements exist in enumeration.
   */
  public boolean hasNextElement() {
    return resultsEnumeration.hasMoreElements();
  }

  /**
   * Returns a copy of the next record in this enumeration,
   */
  public byte[] nextRecord() {
    return (byte[]) resultsEnumeration.nextElement();
  }

  /**
   * The following are simply stubs that we don't implement...
   */
  public boolean hasPreviousElement() {
    return false;
  }

  public void destroy() {
  }

  public boolean isKeptUpdated() {
    return false;
  }

  public void keepUpdated(boolean b) {
    return;
  }

  public int nextRecordId() {
    return 0;
  }

  public int numRecords() {
    return 0;
  }

  public byte[] previousRecord() {
    return null;
  }

  public int previousRecordId() {
    return 0;
  }

  public void rebuild() {
    return;
  }

  public void reset() {
    return;
  }
}

/*
 * Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
 */

/**
 * This class implements the RecordFilter interface. It works on the records
 * created by SimpleRecord. It filters on first name and/or last name.
 */

class SimpleFilter implements RecordFilter {

  // first and last names on which to filter
  private String first;

  private String last;

  /**
   * Public constructor: stores the first and last names on which to filter.
   * Stores first/last names as lower case so that filters are are
   * case-insensitive.
   */
  public SimpleFilter(String f, String l) {
    first = f.toLowerCase();
    last = l.toLowerCase();
  }

  /**
   * Takes a record, (r), and checks to see if it matches the first and last
   * name set in our constructor.
   * 
   * Extracts the first and last names from the record, converts them to lower
   * case, then compares them with the values extracted from the record.
   * 
   * return true if record matches, false otherwise
   */
  public boolean matches(byte[] r) {

    String f = SimpleRecord.getFirstName(r).toLowerCase();
    String l = SimpleRecord.getLastName(r).toLowerCase();

    return f.startsWith(first) && l.startsWith(last);
  }
}

/**
 * This class implements the RecordComparator interface. It works on the records
 * created by SimpleRecord. It sorts on either first name or last name.
 */

class SimpleComparator implements RecordComparator {

  /**
   * Sorting values (sort by first or last name)
   */
  public final static int SORT_BY_FIRST_NAME = 1;

  public final static int SORT_BY_LAST_NAME = 2;

  /**
   * Sort order. Set by constructor.
   */
  private int sortOrder = -1;

  /**
   * Public constructor: sets the sort order to be used for this
   * instantiation.
   * 
   * Sanitize s: if it is not one of the valid sort codes, set it to
   * SORT_BY_LAST_NAME silently. s the desired sort order
   */
  SimpleComparator(int s) {
    switch (s) {
    case SORT_BY_FIRST_NAME:
    case SORT_BY_LAST_NAME:
      this.sortOrder = s;
      break;
    default:
      this.sortOrder = SORT_BY_LAST_NAME;
      break;
    }
  }

  /**
   * This is the compare method. It takes two records, and depending on the
   * sort order extracts and lexicographically compares the subfields as two
   * Strings.
   * 
   * r1 First record to compare r2 Second record to compare return one of the
   * following:
   * 
   * RecordComparator.PRECEDES if r1 is lexicographically less than r2
   * RecordComparator.FOLLOWS if r1 is lexicographically greater than r2
   * RecordComparator.EQUIVALENT if r1 and r2 are lexicographically equivalent
   */
  public int compare(byte[] r1, byte[] r2) {

    String n1 = null;
    String n2 = null;

    // Based on sortOrder, extract the correct fields
    // from the record and convert them to lower case
    // so that we can perform a case-insensitive compare.
    if (sortOrder == SORT_BY_FIRST_NAME) {
      n1 = SimpleRecord.getFirstName(r1).toLowerCase();
      n2 = SimpleRecord.getFirstName(r2).toLowerCase();
    } else if (sortOrder == SORT_BY_LAST_NAME) {
      n1 = SimpleRecord.getLastName(r1).toLowerCase();
      n2 = SimpleRecord.getLastName(r2).toLowerCase();
    }

    int n = n1.compareTo(n2);
    if (n < 0) {
      return RecordComparator.PRECEDES;
    }
    if (n > 0) {
      return RecordComparator.FOLLOWS;
    }

    return RecordComparator.EQUIVALENT;
  }
}

/*
 * Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved.
 */

/**
 * This class provides static methods that allow us to hide the format of a
 * record. N.B. no synchronized access is provided
 */

final class SimpleRecord {

  private final static int FIRST_NAME_INDEX = 0;

  private final static int LAST_NAME_INDEX = 20;

  private final static int FIELD_LEN = 20;

  private final static int PHONE_INDEX = 40;

  private final static int MAX_REC_LEN = 60;

  private static StringBuffer recBuf = new StringBuffer(MAX_REC_LEN);

  // Don't let anyone instantiate this class
  private SimpleRecord() {
  }

  // Clear internal buffer
  private static void clearBuf() {
    for (int i = 0; i < MAX_REC_LEN; i++) {
      recBuf.insert(i, ' ');
    }
    recBuf.setLength(MAX_REC_LEN);
  }

  /**
   * Takes component parts and return a record suitable for our address book.
   * 
   * return byte[] the newly created record first record field: first name
   * last record field: last name num record field: phone number
   */
  public static byte[] createRecord(String first, String last, String num) {
    clearBuf();
    recBuf.insert(FIRST_NAME_INDEX, first);
    recBuf.insert(LAST_NAME_INDEX, last);
    recBuf.insert(PHONE_INDEX, num);
    recBuf.setLength(MAX_REC_LEN);
    return recBuf.toString().getBytes();
  }

  /**
   * Extracts the first name field from a record. return String contains the
   * first name field b the record to parse
   */
  public static String getFirstName(byte[] b) {
    return new String(b, FIRST_NAME_INDEX, FIELD_LEN).trim();
  }

  /**
   * Extracts the last name field from a record. return String contains the
   * last name field b the record to parse
   */
  public static String getLastName(byte[] b) {
    return new String(b, LAST_NAME_INDEX, FIELD_LEN).trim();
  }

  /**
   * Extracts the phone number field from a record. return String contains the
   * phone number field b the record to parse
   */
  public static String getPhoneNum(byte[] b) {
    return new String(b, PHONE_INDEX, FIELD_LEN).trim();
  }
}