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