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...

                                                    Back To My Blog Page

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);
        }
       }

 

                                                                                        Back To My Programs Page