Maybe You Have To Be
Old To Know This
I
started working a night job in December, selling Sprint phones at night. After
about 6 weeks of concentrating on sales I finally started thinking about
programming these devices. So I turned my browser to the tutorial section on http://java.sun.com
and looked at the samples provided by Sun on MIDlet programming. Anyone familiar
with Java nomenclature might assume, correctly, that a MIDlet is similar to an
applet; i.e., there is no main() routine and any MIDlet extends a class called
MIDlet. The MIDlet class, like the Applet class, has a required interface (not
the same as an Applet) that the Java runtime environment expects; startApp(),
pauseApp() and destroyApp(). ...and of course, the MIDlet is not a "stand
alone" application (it only runs in it's own sandbox that resides on a MIDP
device - Mobile Information Device Profile). Now we can get back to the sample
program I looked at; QuotesMIDlet.
QuotesMIDlet
is a fascinating program to study MIDlet programming with. QuotesMIDlet lets you
enter a stock symbol (e.g., GM) and get it's current value from the Yahoo
financial page. A Ticker (a MIDlet class) is created and/or updated as you add
stock symbols, so you can see the stock values scrolling across your phone
screen. There are three parts to this program; (1) QuotesMIDlet.java //the
controlling program, (2) StockDB.java //persistent data for storage and the
methods called by QuotesMIDlet to manipulate it, (3) Stock.java //static
strings used by persistent storage and parsing methods. So far so good,
but there aren't any methods for updating current records or deleting them. The
program author recommends writing these needed methods as an exercise for the
student programmer.
Personally,
I like the idea of keeping the old records so you can see how the stock values
are fluctuating, but being able to delete records you don't want or need anymore
is crucial (phones have limited memory but this is still a method you would need
on Blue Gene). Some example code in the tutorial shows how this could be done
but the example is inefficient and would lead to horribly slow program
performance. The program author appears to be a skillful programmer (except for
his deletion method recommendation) so I'm assuming his recommendation was made
to be strictly educational; it introduced another useful class (Comparator) and
showed how it could be used for such a task.
I
highly recommend you study this excellent tutorial (assuming you are a MIDP
newbee) at http://developers.sun.com/mobility/midp/articles/persist/index.html
and then think about why the author tells you that you need to compare records
as opposed to just deleting them in the order you find them. As you add records
you create a new index value for the record in your persistent data table. As
long as you don't delete any records, the index into the data table stays the
same as the index values associated with your screen display. However, if you
delete a record then the index for all following records in the screen display
decreases by 1. Now if you try delete a record that followed the record you just
deleted it won't work; the index value on the screen no longer matches the index
value in the persistent data table. The tutorial suggests that you get the
string representing the stock listed on your screen display and compare it to
all the string values stored in the persistent data table. Once you get a match
you can delete the record from your persistent data. This works but it's
inefficient and gets worse as you add data records. So what can you do? You can
create a table index and maintain it.
The
tutorial describes how you can create an index table into your persistent
data table, or record store. However, it only suggests this so you can loop
through a set of possible index values (and you could compare these index values
as opposed to a string comparison). But if you delete a record from the record
store, you can delete the same record from your index table (I used a Vector and
so did the program author) and then trim the table (Vector). Now your index
table has matching records with the screen display. Say you select record 3 on
your screen, then get record 3 in your index table. Record 3 in your index table
holds the index value of the corresponding record in your record store, so you
can delete it. Next delete the record in your index table and then trim the
table (Vector has a nice method for this). Refresh your screen to display the
records in your record store and all three tables, the record store, screen
display and the index table all have the same corresponding records.
Why
did I title this article, "Maybe You Have to Be Old..."? In the
prehistoric days of programming, before the advent of ISAM files, people created
their own index files for their data files. Back in those days, just like today,
a file might have wide records with lots of wide fields. Reading such a record
is a time consuming event and you still had to parse it and probably had to
perform a lot of tests to make sure you were reading the right field, etc. If
you know ahead of time what field you need to work with you can create a file
with records that consist of that field and nothing else. Now if you have to
search through the file for info you want it will go a lot faster (the records
will be a lot smaller) and you'll know what record you'll have to retrieve
because you'll have it's record number. If you delete that record, you need to
delete it from your index file too maintain the record correspondence between
files (note that I used a table of index files and maintained record order
between two tables). Another use of an index file is if you are maintaining a database. You don't want to reorganize your database file
(consisting of one or more tables) every time you perform
an operation on it because that would cause your application to be unacceptably
slow. Instead you could keep a table of index values (two fields this time - the
index value and the key field). Once again this would require
maintaining the index file; e.g., if you delete a
record you have to delete it from the index file, too - we still need to
maintain a 1 to 1 correspondence between the record numbers in two tables.
Another scheme is to have a table of index values and corresponding record
numbers, which allows you to sort a table by sorting the index file (dBase
worked this way). Maybe you have to be
old...
Here's
the code I modified so you can delete records from the record store:
import javax.microedition.rms.*; import javax.microedition.lcdui.*; import javax.microedition.midlet.*; import javax.microedition.io.*; import java.io.*; import java.util.Vector;
public class QuotesMIDlet extends MIDlet implements CommandListener { Vector recordIDs = new Vector(); int lastID = 0; Display display = null; List menu = null; // main menu List choose = null; TextBox input = null; Ticker ticker = new Ticker("Database Application"); String quoteServer = "http://quote.yahoo.com/d/quotes.csv?s="; String quoteFormat = "&f=slc1wop"; // The only quote format supported
static final Command backCommand = new Command("Back", Command.BACK, 0); static final Command mainMenuCommand = new Command("Main", Command.SCREEN, 1); static final Command saveCommand = new Command("Save", Command.OK, 2); static final Command exitCommand = new Command("Exit", Command.STOP, 3); static final Command delCommand = new Command("Delete", Command.OK, 4); String currentMenu = null;
// Stock data String name, date, price;
// record store StockDB db = null;
public QuotesMIDlet() { // constructor }
// start the MIDlet public void startApp() throws MIDletStateChangeException { display = Display.getDisplay(this); // open a db stock file try { db = new StockDB("mystocks"); } catch(Exception e) {} menu = new List("Stocks Database", Choice.IMPLICIT); menu.append("List Stocks", null); menu.append("Add A New Stock", null); menu.append("Delete A Stock", null); menu.addCommand(exitCommand); menu.setCommandListener(this); menu.setTicker(ticker);
mainMenu(); }
public void pauseApp() { display = null; choose = null; menu = null; ticker = null;
try { db.close(); db = null; } catch(Exception e) {} }
public void destroyApp(boolean unconditional) { try { db.close(); } catch(Exception e) {} notifyDestroyed(); }
void mainMenu() { display.setCurrent(menu); currentMenu = "Main"; }
// Construct a running ticker // with stock names and prices public String tickerString() { StringBuffer ticks = null; try { RecordEnumeration enum1 = db.enumerate(); ticks = new StringBuffer(); while(enum1.hasNextElement()) { String stock1 = new String(enum1.nextRecord()); ticks.append(Stock.getName(stock1)); ticks.append(" @ "); ticks.append(Stock.getPrice(stock1)); ticks.append(" "); } } catch(Exception ex) {} return (ticks.toString()); }
// Add a new stock to the record store // by calling StockDB.addNewStock() public void addStock() { input = new TextBox( "Enter a Stock Name:", "", 5, TextField.ANY); input.setTicker(ticker); input.addCommand(saveCommand); input.addCommand(backCommand); input.setCommandListener(this); input.setString(""); display.setCurrent(input); currentMenu = "Add"; recordIDs.addElement(new Integer(++lastID)); }
//menu for delete a stock public void delStock() { choose = new List("Delete A Stock", Choice.EXCLUSIVE); choose.setTicker(new Ticker(tickerString())); choose.addCommand(backCommand); choose.addCommand(delCommand); choose.setCommandListener(this); try { RecordEnumeration re = db.enumerate(); while(re.hasNextElement()) { String theStock = new String(re.nextRecord()); choose.append(Stock.getName( theStock)+" @ " + Stock.getPrice(theStock), null); } } catch(Exception ex) {} display.setCurrent(choose); currentMenu = "DeleteMenu"; }
// Connect to quote.yahoo.com and // retrieve the data for a given // stock symbol. public String getQuote(String input) throws IOException, NumberFormatException { String url = quoteServer + input + quoteFormat; StreamConnection c = (StreamConnection)Connector.open( url, Connector.READ_WRITE); InputStream is = c.openInputStream(); StringBuffer sb = new StringBuffer(); int ch; while((ch = is.read()) != -1) { sb.append((char)ch); } return(sb.toString()); }
// List the stocks in the record store public void listStocks() { choose = new List("Choose Stocks", Choice.MULTIPLE); choose.setTicker( new Ticker(tickerString())); choose.addCommand(backCommand); choose.setCommandListener(this); try { RecordEnumeration re = db.enumerate(); while(re.hasNextElement()) { String theStock = new String(re.nextRecord()); choose.append(Stock.getName( theStock)+" @ " + Stock.getPrice(theStock), null); } } catch(Exception ex) {} display.setCurrent(choose); currentMenu = "List"; }
// Handle command events public void commandAction(Command c, Displayable d) { String label = c.getLabel(); if (label.equals("Exit")) { destroyApp(true); } else if (label.equals("Delete")) { if(currentMenu.equals("DeleteMenu")) { //get selected element from list int selected = choose.getSelectedIndex(); //get record store index from recordIDs vector Integer id = (Integer) recordIDs.elementAt(selected); int ndx = id.intValue(); //delete it from the record store db.delAStock(ndx); //delete it from the vector, too recordIDs.removeElement(id); //now trim the vector recordIDs.trimToSize(); //refresh the screen delStock(); } } else if (label.equals("Save")) { if(currentMenu.equals("Add")) { // add it to database try { String userInput = input.getString(); String pr = getQuote(userInput); db.addNewStock(pr); ticker.setString(tickerString()); } catch(IOException e) { } catch(NumberFormatException se) { } mainMenu(); } } else if (label.equals("Back")) { if(currentMenu.equals("List")) { // go back to menu mainMenu(); } else if(currentMenu.equals("Add")) { // go back to menu mainMenu(); } else if(currentMenu.equals("DeleteMenu")) { // go back to menu mainMenu(); } } else { List down = (List)display.getCurrent(); switch(down.getSelectedIndex()) { case 0: listStocks();break; case 1: addStock();break; case 2: delStock();break; } } } }
public class Stock { private static String name, time, price; // Given a quote from the server, // retrieve the name, //price, and date of the stock public static void parse(String data) { int index = data.indexOf('"'); name = data.substring(++index, (index = data.indexOf('"', index))); index +=3; time = data.substring(index, (index = data.indexOf('-', index))-1); index +=5; price = data.substring(index, (index = data.indexOf('<', index))); } // get the name of the stock from // the record store public static String getName(String record) { parse(record); return(name); } // get the price of the stock from // the record store public static String getPrice(String record) { parse(record); return(price); }
}
import javax.microedition.rms.*; import java.util.Enumeration; import java.util.Vector; import java.io.*;
public class StockDB { RecordStore recordStore = null; public StockDB() {} public StockDB(String fileName) { try { recordStore=RecordStore.openRecordStore(fileName, true); } catch(RecordStoreException rse) { rse.printStackTrace(); } }
public void close() throws RecordStoreNotOpenException, RecordStoreException { if (recordStore.getNumRecords() == 0) { String fileName = recordStore.getName(); recordStore.closeRecordStore(); recordStore.deleteRecordStore(fileName); } else { recordStore.closeRecordStore(); } }
public synchronized void addNewStock(String record) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(baos); try { outputStream.writeUTF(record); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); } byte[] b = baos.toByteArray(); try { recordStore.addRecord(b, 0, b.length); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } }
public synchronized void delAStock(int id) { try { recordStore.deleteRecord(id); } catch(Exception e) {}; }
public synchronized RecordEnumeration enumerate() throws RecordStoreNotOpenException { return recordStore.enumerateRecords(null, null, false); } }