Maintenance Programming and J2ME

I mentioned in a previous posting that I was studying J2ME by looking at the examples on java.sun.com. I also took a look at a book that was online via the Broward County public library. Next I checked out a book from the Broward County public library; Sam's Teach Yourself Wireless Java with J2ME in 21 Days. This is a really great book, not just because it gives excellent instructions on J2ME programming, but because the author also gives some really good programming advice. On day 11 (there are 21 chapters, named day 1, day 2, etc.) the author, Michael Morrison, explains how to write a program that will accept two addresses as input and give directions on how to get from address one to address two as output; Directions.java. Directions gets it's directions from mapquest.com. Michael Morrison gave some really great advice in this chapter, "Of course, this makes the Directions MIDlet dependent on the format of the Mapquest directions page. You will have to update MIDlet if Mapquest ever changes the format of their directions page.".  

When you write a program that has parts that will probably change in the future because of changing data inputs, output requirements, environment changes, etc., you want to make sure those changing parts are written as separate subroutines, methods, functions, etc (whatever your language supports). At the very least, you want that code to be easily separated from the rest of your code. Directions is a pretty simple program, with two basic methods (three if you include the commandAction method); a constructor that creates an input page and a getDirections() method that (1) extracts the address info from input page that was created by the constructor, (2) creates a connection to mapquest.com using the address info from (1) as cgi arguments, (3) captures the web page that mapquest.com sends us, (4) parses the directions information from the web page and sends it to a form that is displayed on the MIDP device (my Treo 650 in this case).  Michael Morrison did a great job delineating the four parts of getDirections(), which made it easy for me to modify steps (2) and (4).  I.e., mapquest.com changed the cgi string required for calling up a directions page (step (2)) and changed the format of the created html (step (4)).

I'll make a plug for the book. Buy Michael Morrison's book (or check it out of a library like I did) and study day 11. Then look for the differences between the listings in that chapter and my code listed below. One thing you should notice is that Michael Morrison created his output on the fly; i.e., while reading the mapquest.com output page. This was probably necessary when the book was written, 2001, due to the limited amount of memory in phones back then. My Treo 650 was a high end PDA when it came out in 2004 and has 32 Mbytes of  RAM plus external storage (a memory card). I set the preferences on my PDA to use 4 Mbytes for programs and to use a 16 Kbyte program stack. So my MIDlet has no problem reading in the entire web page and storing it before parsing the info in it; i.e., my steps (3) and (4) are distinct, whereas Michael Morrison combines them. Even low end phones today have enough memory to run Directions.java the way I re-wrote it. This is an example of how improving technology can affect how we write programs. 

Back To My Blog Page

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;
import java.io.*;
import javax.microedition.io.*;
public class Directions extends MIDlet implements CommandListener {
  private Command exitCommand, goCommand, backCommand;
  private Display display;
  private Form locationsScreen;
  private TextField addrFromField, zipFromField, addrToField, zipToField;
  private Form directionsScreen;
  public Directions() {
    // Get the Display object for the MIDlet
    display = Display.getDisplay(this);
    // Create the Exit, Go, and Back commands
    exitCommand = new Command("Exit", Command.EXIT, 2);
    goCommand = new Command("Go", Command.OK, 2);
    backCommand = new Command("Back", Command.BACK, 2);
    // Create the locations screen form
    locationsScreen = new Form("Enter Locations");
    addrFromField = new TextField("From Address", "1440 NW 70th Ter", 30, TextField.ANY);
    locationsScreen.append(addrFromField);
    zipFromField = new TextField("From Zip Code", "33063", 5, TextField.NUMERIC);
    locationsScreen.append(zipFromField);
    addrToField = new TextField("To Address", "2000 N. State Road 7", 30, TextField.ANY);
    locationsScreen.append(addrToField);
    zipToField = new TextField("To Zip Code", "33063", 5, TextField.NUMERIC);
    locationsScreen.append(zipToField);
    // Set the Exit and Go commands for the locations screen
    locationsScreen.addCommand(exitCommand);
    locationsScreen.addCommand(goCommand);
    locationsScreen.setCommandListener(this);
    // Create the directions screen form
    directionsScreen = new Form("Directions");
    // Set the Back command for the directions screen
    directionsScreen.addCommand(backCommand);
    directionsScreen.setCommandListener(this);
  }
  public void startApp() throws MIDletStateChangeException {
    // Set the current display to the locations screen
    display.setCurrent(locationsScreen);
  }
  public void pauseApp() 
  {
  }
  public void destroyApp(boolean unconditional) {
  }
  public void commandAction(Command c, Displayable s) {
    if (c == exitCommand) {
      destroyApp(false);
      notifyDestroyed();
    }
    else if (c == goCommand) {
      // Get the directions
      getDirections(addrFromField.getString().toLowerCase(),
        zipFromField.getString().toLowerCase(),
        addrToField.getString().toLowerCase(),
        zipToField.getString().toLowerCase());
    }
    else if (c == backCommand) {
      // Set the current display back to the locations screen
      display.setCurrent(locationsScreen);
      // Clear the directions
      for (int i = directionsScreen.size() - 1; i >= 0; i--)
        directionsScreen.delete(i);
    }
  }
  private String replaceSpaces(String s) {
    StringBuffer str = new StringBuffer(s);
    // Read each character and replace if it's a space
    for (int i = 0; i < str.length(); i++) {
      if (str.charAt(i) == ' ') {
        str.deleteCharAt(i);
        str.insert(i, "%20");
      }
    }
    return str.toString();
  }
  private void getDirections(String addrFrom, String zipFrom,
    String addrTo, String zipTo) 
  {
    StreamConnection conn = null;
    InputStream in = null;
    StringBuffer data = new StringBuffer();
    // Replace any spaces in the addresses
    addrFrom = replaceSpaces(addrFrom);
    addrTo = replaceSpaces(addrTo);
    // Build the URL for the directions page
    // The rest of the code has been changed from the original due to changes in mapquest.com, DMK
    String url = "http://www.mapquest.com/directions/main.adp?go=1&do=nw&rmm=1&un=m&cl=EN" + 
                 "&ct=NA&rsres=1&1ffi=&1l=&1g=&1pl=&1v=&1n=&2ffi=&2l=&2g=&2pl=&2v=&2n=&1pn=&1a=" 
                 + addrFrom + "&1c=&1s=&1z=" + zipFrom +
                 "&2pn=&2a=" + addrTo + "&2c=&2s=&2z=" + zipTo + "&r=f";
    try 
    {
      // Open the HTTP connection
      conn = (StreamConnection)Connector.open(url);
      // Obtain an input stream for the connection
      in = conn.openInputStream();
      // Read a line at a time from the input stream
      int ch;     
      
      //capture web page to a string buffer
      while ((ch = in.read()) != -1)
      { if (ch != '\n')       
        data.append((char)ch);
      }
    }
    catch (IOException e) 
    {
      System.err.println("The connection could not be established.");
    }
      String dataStr = data.toString();      
      int pageSize = data.length();
      int startPos = 0;
      int endPos = 0;
      data = new StringBuffer();
      String searchStr = "";
      String appStr = "";
      
      //get directions info
      int dirNum = 0;
      for (int cntr = 1; cntr < pageSize; cntr++)         
        {
          //look for directions step
          searchStr =  "<td class=\"num\">" + cntr + ":</td>";
          startPos = dataStr.indexOf(searchStr);
          if (startPos == -1) 
              break;
          else
              dirNum++;
          appStr = Integer.toString(cntr) + '.' + ' ';
          data.append(appStr);
          //we need to find out where the next direction is
          //so we can create a substring that ends there (open ended)
          searchStr =  "<td class=\"num\">" + (cntr + 1) + ":</td>";
          endPos = dataStr.indexOf(searchStr);
          if (endPos == -1) endPos = dataStr.length();
          data.append(dataStr.substring(startPos + 24, endPos)); //good programming practice dictates
          //the use of searchStr.length() instead of 24, but the numeric literal stands out in my code
          //and lets me know this is a section of code that I'm still playing with (it's good now, but
          //I thought I'd leave it as a blog feature)
          if (endPos == dataStr.length()) break;
          dataStr = dataStr.substring(endPos);
        }
       
        //put it on the screen
        int nextPos, lastPos = 0;
        dataStr = data.toString();
        searchStr = "";
        String dispStr = "";        
        for (int cntr = 2; cntr <= dirNum; cntr++)
        {  
          searchStr = Integer.toString(cntr) + '.' + ' '; 
          nextPos = dataStr.indexOf(searchStr);
          if (nextPos == -1) nextPos = dataStr.length();
          dispStr = dataStr.substring(lastPos, nextPos);
          dispStr = cleanup(dispStr);
          directionsScreen.append(new StringItem("","*" + Integer.toString(cntr - 1) + ". " + dispStr));
          if (nextPos == dataStr.length())
              break;
          else
              lastPos = nextPos;
        }  
        display.setCurrent(directionsScreen);
    }  
  
    private String cleanup(String dsp)
    {
        int tagPos = dsp.indexOf("<td>");
        dsp = dsp.substring(tagPos + 4);
        tagPos = dsp.indexOf("</td>");
        dsp = dsp.substring(0, tagPos - 1);
        return dsp;
    }
}

 

Back To My Program List