AddressBookMIDlet.java Source code

Java tutorial

Introduction

Here is the source code for AddressBookMIDlet.java

Source

/*
 * 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();
    }
}