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