Introduction to MIDP/J2ME, Part II

Introduction to MIDP/J2ME, Part II

Use these user-interface and data-store components to create MIDP-based applications

Summary
This second part of Michael Cymerman's MIDP series will focus on the development of an application using non-graphical user interface components. In addition, he'll explore the conversion and storage of application data to the MIDlet RecordStore. These two concepts are discussed in detail through a simple Stock Portfolio management application constructed specifically for this demonstration. (4,700 words)
By Michael Cymerman

 

Part 1 of this series was focused on the deployment of the J2MEWTK environment and a rudimentary exploration of the MIDP APIs. This part of the series will focus on the development of an application using the pre-canned user interface components provided in the MIDP API. In addition, I'll explore the conversion and storage of application data to the MIDlet RecordStore. These two concepts will be discussed in detail through a simple Stock Portfolio management application constructed specifically for this article.

 Device programming with MIDP: Read the whole series!  
  • Part 1. Build devices with MIDP APIs and J2ME across multiple wireless platforms.
  • Part 2. Use these user-interface and data-store components to create MIDP-based applications.
  • The hierarchy of Displayable objects
    In the examples found in Part 1, I discussed the two major categories of Displayables: Canvas and Screen.

    Canvas is a type of Displayable in which the developer accepts responsibility for creating the entire user interface. This is an extremely useful interface for creating complex graphical interfaces, such as those used in video games. If you choose this path, the UI is drawn on a Canvas object similar to the AWT Canvas used in applets.

    Screen is a type of Displayable in which you use predefined components to assemble a user interface. The components are similar to those AWT components used in constructing applets, such as Label and TextField. If you choose this path, you will need to add components to a subclass of the abstract Screen object when necessary to construct your user interface.

    I will focus on the Screen type of Displayable object in this part of the series, since the Canvas object was sufficiently discussed Part 1. As I mentioned above, the Screen object is an abstract class of Displayable. The subclasses of the Screen class are Alert, Form, List, and TextBox.

    In this article, I will examine the mechanics of these Displayable objects, including the construction, interaction, and event-handling schemes that enable them to come together to form an application.

    Detailed example
    The best way to learn how to use some of these screens is through an example. The example used here contains some workflow that demonstrates the following concepts:

     

    • Construction with different screen types
    • Construction with different screen components
    • Usage of commands from within the javax.microedition.lcdui classes
    • An implementation of MVC for efficient screen manipulation
    • Interaction with the javax.microedition.rms data store on the device

    To provide this information, I will use a simple rendition of a brokerage application. In this application, the user can buy a stock or sell a stock that he or she currently owns. Please understand that this process has been highly simplified in order to demonstrate key concepts.

    In Part 3 of this series, I will expand this application to connect with a ticker service to retrieve accurate pricing for the purchase-using HTTP.

    ExampleMidlet
    The ExampleMidlet provides the context for this application. It controls the flow and provides access to the device resources as defined in the MIDP specification.

    public class ExampleMidlet extends MIDlet implements Controller

    As shown, the ExampleMidlet implements the Controller interface to manage the screen interactions. The intent of the Controller interface is to provide the services needed by both the view and model classes.

    Controller interface
    The Controller interface contains methods to access the StockDatabase, which is a simple RMS data store, and to manipulate the Display object associated with the MIDlet. The Controller interface in this example is a simple interface that you can expand to include methods to retrieve properties, make HTTP connections, or whatever your application requirements deem necessary.

    public interface Controller
    {
    public StockDatabase getStockDatabase();
    public Displayable currentScreen();
    public void nextScreen(Displayable display);
    public void lastScreen();
    }

    By implementing the Controller interface, the ExampleMidlet can expose certain methods to the individual screens. You could instead pass the MIDlet as a parameter to the screens, but you probably don't want to interfere with the normal MIDlet lifecycle.

    Within the ExampleMidlet, the nextScreen() and lastScreen() methods have been used to maintain the information about the currently visible screen. These methods use the java.util.Stack object to maintain the display state.

    The application will push the currently displayed Displayable object to the Stack prior to displaying the next screen when nextScreen is called.

    public void nextScreen(Displayable display){
        Displayable currentScreen = this.getDisplay().getCurrent();

        if ( currentScreen != null)
       _screenStack.push( currentScreen );

        getDisplay().setCurrent( display);
    }

    The application will pop the previously displayed Displayable object from the Stack and set the display to show that screen.

    public void lastScreen()
    {
        Displayable display = null;

        if ( !_screenStack.empty() )
       display = (Displayable) _screenStack.pop();
        else
       display = new WelcomeScreen( (Controller) this );

        getDisplay().setCurrent( display);
    }

    There are other ways in which you could handle the management of the screens. For example, you could read in a screen from the properties file and use that list to manage the interactions between the display screens. This approach provides the necessary management for this example without suffering through a more complex approach.

    WelcomeScreen
    The first screen that is visible to the user is the WelcomeScreen. Let's walk through the code for this screen. The WelcomeScreen extends from the javax.microedition.lcdui.List object and implements the CommandListener interface.

    public class WelcomeScreen extends List implements CommandListener

    The constructor is passed a parameter that contains a reference to the Controller as an argument, which will be stored as a private variable for future use.

    public WelcomeScreen(Controller controller)

    Because the WelcomeScreen extends the List object, the superclass must be called for instantiation. The parameters passed are the title of the screen and the type of list.

    super("Welcome", List.IMPLICIT);

    List
    In this case, the list is of type IMPLICIT. The list types that you can use are shown in Table 1.

    Table 1. List types defined
    List Type Definition
    IMPLICIT Neither checkboxes nor radio buttons are visible next to each item in a list. Users can select only one item from the list. Similar to an HTML Option list.
    EXPLICIT Radio buttons allow users to select only one item from the list.
    MULTIPLE Checkboxes allow users to select multiple items from the list.

    Ticker
    The WelcomeScreen also contains a ticker that generates a simple welcome message to the user. To create a ticker, call its constructor with a String object of the message to be scrolled and then call the setTicker() method of the Screen object to add the ticker to the heading.

            Ticker ticker =
                 new Ticker("Thank you for viewing the Stock Application Example");

            this.setTicker( ticker);

    Using the append() method, you can add each item to the list for display.

            append("Buy Stock", null);
            append("Sell Stock", null);

    The WelcomeScreen will listen and handle any commands that are initiated when it is displayed. Therefore, it calls the setCommandListener() method with itself as a parameter.

            this.setCommandListener(this);

    CommandListener
    The event-handling infrastructure in the MIDlet architecture, briefly discussed in the WelcomeScreen example, lets you handle events generated from the MIDlet Screen objects.

    Events are generated on user actions, such as clicking the soft-key menu buttons on a Sreen or selecting items from a List. When these events are generated, the commandAction() method of the objects that have registered interest in the current screen will be executed.

    For the WelcomeScreen example, the user is presented with a list containing two items: "Buy Stock" and "Select Stock." When the user selects one of the items by using the up/down arrows and then clicking the action button, the commandAction() method will be called.

        public void commandAction (Command c, Displayable d)
        {
            if ( c == List.SELECT_COMMAND)
            {
                List shown = (List) _controller.currentScreen();

                switch (shown.getSelectedIndex())
                {
                    case 0:
                        _controller.nextScreen( new BuyStockScreen( _controller) );
                        break;
                    case 1:
                    default:
                        _controller.nextScreen( new SelectStockScreen(_controller) );
                }
            }
        }

    This commandAction() method demonstrates how to capture the events generated by an IMPLICIT list. The first step involves retrieving the list from the display, which in this example is done through the Controller interface. The selected item can be retrieved from the list to allow the application to perform logical operations to determine the proper course of action. In this case, the next display object will be instantiated and passed to the controller's nextScreen() method.

    BuyStockScreen
    Continuing the example, suppose the user has selected the "Buy Stock" option from the Welcome screen. In this section, I will examine the BuyStockScreen object that would be instantiated in that case.

    public class BuyStockScreen extends StockScreen implements CommandListener

    The BuyStockScreen class extends the StockScreen class, which itself extends the javax.microedition.lcdui.Form class.

    StockScreen
    The constructor is passed the title of the screen and a reference to the controller object, which is to be stored in a protected variable for use by the child class. The title is passed because the StockScreen object is inherited by the BuyStockScreen, SelectStockScreen, and SellStockScreen classes.

        public StockScreen(String title, Controller controller)
        {
            super(title);

            _controller = controller;
            _stockDB = controller.getStockDatabase();
        }

    One item of interest in the constructor is the fact that the StockScreen class is retrieving a reference to the StockDatabase interface for use within the child classes.

        public void displayScreen(String symbol, int numShares)
        {
            _symbolField = new StringItem("Stock Symbol: ", symbol);
            _numSharesField = new StringItem("Num Shares: ", ""+ numShares);

            append(_symbolField);
            append(_numSharesField);

            generateButtons();
        }

        public void displayScreen()
        {
            _symbolEntry = new TextField("Enter Stock Symbol", "", 6, TextField.ANY);
            _numEntry = new TextField("Enter Num Shares", "", 10, TextField.NUMERIC);

            append(_symbolEntry);
            append(_numEntry);

            generateButtons();
        }

    There are two different displayScreen() methods to illustrate two different user interface components: StringItem and TextField.

    StringItem
    A StringItem is a display-only item that contains a label and a corresponding value. In this case, the StringItem is used to display the chosen stock and the number of shares for the sell order.

    TextField
    A TextField is a UI component that lets the user enter information from the phone keypad to the application. The TextField takes the following parameters:

     

    • Label
    • Initial text
    • Maximum length
    • Field constraint

    Table 2 illustrates the different field constraints that may be used by default.

    Table 2. Description of different field constraints
    Field Constraint Description
    ANY The user is allowed to enter any text
    EMAILADDR The user is allowed to enter an email address
    NUMERIC The user is allowed to enter only an integer value
    PASSWORD The text entered must be masked so that typed characters are not visible
    PHONENUMBER The user is allowed to enter a phone number
    URL The user is allowed to enter a URL

    You should note that different implementations of the MIDP specification may have differing behavior. For example, the phone number field may contain additional characters, such as the + sign commonly used in European phone numbers. The application must remain sensitive to these varying implementation details until the specification becomes more standardized.

    Both of the displayScreen() methods will call the generateButtons() method to create the "Next" and "Back" buttons used on all three of the subclass screens. In this method, each Command object is instantiated and in turn added to the screen using the addCommand() method.

        public void generateButtons()
        {
            this.nextCommand = new Command("Next", Command.SCREEN, 1);
            this.backCommand = new Command("Back", Command.BACK, 1);

            this.addCommand(backCommand);
            this.addCommand(nextCommand);
        }
    }

    BuyStockScreen revisited
    I will now return to the BuyStockScreen. Because the superclass has handled all of the initialization, this screen needs only to set up its CommandListener actions.

        public BuyStockScreen(Controller controller)
        {
            super("Buy Stock", controller);

            super.displayScreen();

            this.setCommandListener(this);
        }

    In the commandAction() method, the first check is to determine if the user has clicked on the "Back" button. If so, the controller is called to display the previous screen retrieved from the Stack.

        public void commandAction(Command c, Displayable d)
        {
            if ( c == backCommand)
            {
                _controller.lastScreen();
            }

    If the user has instead clicked on the "Next" button, the process of buying a stock will begin. In this process, the parameters from the TextField must be retrieved. The buyStock() method of the StockDatabase interface will then be called to add the newly purchased stock to the stock database. If this proceeds without any exceptions, the WelcomeScreen will once again be displayed to the user.

            else
            {
                // buy the shares
                try
                {
                    String symbol = _symbolEntry.getString();
                    String shares = _numEntry.getString();

                    _stockDB.buyStock( symbol, shares, 4);

                    _controller.nextScreen( new WelcomeScreen( _controller) );
                }
                catch (StockException se)
                {
                    this.setTitle("Bad Data");

                    se.printStackTrace();
                }
            }

    StockDatabase
    The StockDatabase interface is used to abstract the functionality of the MIDP storage implementation from the application. Because the UI developer does not need to understand the technical details of the data storage, an abstraction of this level can be helpful.

    public interface StockDatabase
    {
        public void buyStock(String symbol, String shares, int price) throws StockException;
        public void sellStock(Stock stock, int numShares) throws StockException;
        public Vector getStocks() throws StockException;
        public Stock getStock(String stock) throws StockException;
    }

    The StockDatabase interface contains methods to add, retrieve, and remove data from the MIDlet RecordStore. The StockDatabase and corresponding data store will be discussed in further detail following my discussion of the UI components.

    SelectStockScreen
    The SelectStockScreen presents the user with a list of stocks in a personal portfolio as retrieved from the database. The constructor of this object is responsible for initializing the display. Recall from the StockScreen discussion that the StockDatabase reference _stockDB has been retrieved in the superclass constructor. The getStocks() method is called to retrieve a list of the stocks in the user's portfolio. If any are found, a Vector containing those stocks will be returned and passed to the showStockList() method for conversion into a friendly user interface.

        public SelectStockScreen(Controller controller)
        {
            super("Select Stock", controller);

            try
            {
                Vector stocks = _stockDB.getStocks();

                Item displayItem = showStockList(stocks);

                append(displayItem);

                super.generateButtons();

                this.setCommandListener( this);
            }
            catch (StockException se)
            {
                append(se.getMessage() );
            }
        }

    ChoiceGroup
    The showStockList() method is used to retrieve the Stock objects from the Vector of stocks. It then creates a ChoiceGroup object packed with the list of stocks the user can sell. A ChoiceGroup is an Item that you can add to a Form screen. It resembles a list in capabilities and options, but is not a screen type. On this screen, the EXCLUSIVE option for the list has been used to generate a list of stocks to sell so that the user can choose only one stock at a time.

        private Item showStockList(Vector stocks)
        {
            Stock currentStock = null;

            _stockList = new ChoiceGroup("Choose stock", ChoiceGroup.EXCLUSIVE);

            for ( int i = 0; i < stocks.size(); i++)
            {
                currentStock = (Stock) stocks.elementAt(i);
                _stockList.append( currentStock.getSymbol(), null );
            }

            return _stockList;
        }

    The SelectStockScreen's commandAction() method is used to pass the control from the screen where the user selects the stock to sell to the screen that will actually perform the stock sale. This method will retrieve the currently selected stock from the list of stocks and pass that String to the SellStockScreen.

        public void commandAction( Command c, Displayable d)
        {
            if ( c == backCommand)
            {
                _controller.lastScreen();
            }
            else
            {
                // get the selected item
                int selectedIndex = _stockList.getSelectedIndex();

                String selectedStock = _stockList.getString( selectedIndex);

                // move on to the SellStockScreen
                SellStockScreen sellStockScreen =
                    new SellStockScreen(_controller, selectedStock);

                _controller.nextScreen( sellStockScreen);
            }
        }

    SellStockScreen
    The SellStockScreen is used to display a message to the user confirming the sale of the stock that was previously selected. If the user selects the "Next" button, the stock will be removed from the database.

    The constructor of this object will receive a reference to the Controller class, as well as a String representing the stock to be sold. This screen will access the StockDatabase object to retrieve information that relates to the number of shares of that stock currently listed in the database. It will then display that information to the screen using the displayScreen() method described earlier in the StockScreen discussion.

        public SellStockScreen(Controller controller, String selectedStock)
        {
            super("Sell "+ selectedStock, controller);

            append("Selling " + selectedStock);

            try
            {
                // determine how many shares the user has
                _sellStock = _stockDB.getStock( selectedStock);

                _symbol = _sellStock.getSymbol();
                _numShares = _sellStock.getNumShares();

                // display screen
                super.displayScreen(_symbol, _numShares);

                this.setCommandListener( this);

            }
            catch (StockException se)
            {
                System.out.println("EXCEPTION "+ se.getMessage() );
            }
        }

    The commandAction() method will handle the user interaction. Should the user click the "Next" button, the StockDatabase will be accessed to sell all shares of the chosen stock that are present in the database. The SellStockScreen in this example does not allow the user to alter the number of shares that are to be sold. The display will once again return to the WelcomeScreen to show the user the main menu of commands.

        public void commandAction( Command c, Displayable d)
        {
            if ( c == backCommand)
            {
                _controller.lastScreen();
            }
            else
            {
                // sell the shares
                try
                {
                    _stockDB.sellStock( _sellStock, _numShares);
                }
                catch (StockException se)
                {
                    se.printStackTrace();
                }

                _controller.nextScreen( new WelcomeScreen( _controller) );
            }
        }

    Stock object
    The Stock object that has been used throughout the example is a simple object containing a constructor and a set of getXYZ() methods. As a prelude to my discussion of the RecordStore infrastructure, the key methods are presented here:

        public Stock(String symbol, int numShares, int price)
        {
            _symbol = symbol;
            _numShares = numShares;
            _price = price;
        }

        public String getSymbol(){ return _symbol; }
        public int getNumShares(){ return _numShares; }
        public int getPrice(){ return _price; }

    RMSStockStore
    Having completed the discussion of the example's application layer, I'll turn your attention toward the data storage layer. In my prior discussion, the data store had been accessed solely through the StockDatabase interface. I will now examine the object that implements that interface.

    The RMSStockStore is the object in this example that is used to coordinate all activities related to the RecordStore. This object contains a reference to the javax.microedition.rms.RecordStore object that is part of the MIDP APIs.

    public class RMSStockStore implements RecordFilter, RecordComparator, StockDatabase

    The constructor of the RMSStockStore object will instantiate a RecordStore object pointing toward the "shares" data store. It will create that data store if one does not previously exist.

        public RMSStockStore()
        {
           try
           {
               String fileName = "shares";
               recordStore = RecordStore.openRecordStore(fileName, true);
           }
           catch (RecordStoreException rse)
           {
               System.out.println(rse);
               rse.printStackTrace();
           }
        }

    The addStock() method is an internal method that is used by the object to perform the actual writes to the data store. Notice that the data to be stored must be converted from an object or a series of objects to a byte array in order to be added to the record store. This conversion is done with the help of the StockStorage class, which I'll discuss later. Once the byte array is ready for storage, the addRecord() method is called, passing on that byte array to the RecordStore object.

        private void addStock(int shares, String symbolName, int price)
        {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            DataOutputStream outputStream = new DataOutputStream(baos);

            try {
                Stock outputStock = new Stock( symbolName, shares, price);

                StockStorage.writeStock( outputStock, outputStream);

            }
            catch (IOException ioe) {
                System.out.println(ioe);
                ioe.printStackTrace();
            }

            // Extract the byte array
            byte[] b = baos.toByteArray();

            // Add it to the record store
            try {
                recordStore.addRecord(b, 0, b.length);
            }
            catch (RecordStoreException rse) {
                System.out.println(rse);
                rse.printStackTrace();
            }
        }


    The getStockHelper() method is used as a helper to both the getStock() and getStocks() methods. The getStockHelper() method can enumerate over a set of records that has been returned from the RecordStore. In doing so, it will read in those records and add them to a Vector that will be passed back to the user interface objects. Notice that the StockStorage object is also used to convert the InputStream (byte array) into a Stock object.

        private Vector getStockHelper(RecordEnumeration re)
        {
            Vector stockVector = new Vector();

            try {
                while(re.hasNextElement())
                {
                    int id = re.nextRecordId();
                    ByteArrayInputStream bais =
                        new ByteArrayInputStream(recordStore.getRecord(id));
                    DataInputStream inputStream = new DataInputStream(bais);
                    try {
                        Stock stock = StockStorage.readStock(inputStream);

                        stockVector.addElement( stock);
                    }
                    catch (EOFException eofe) {
                        System.out.println(eofe);
                        eofe.printStackTrace();
                    }
                }
            }
            ...

            return stockVector;
        }

    RecordStore
    In the code already discussed, there are references to the javax.microedition.rms.RecordStore object. The following is a subset of the methods contained within that object that I have used in this example:

     

    • openRecordStore(): Used to open/create a record store
    • addRecord(): Used to add a record to that store
    • enumerateRecords(): Used to loop over the records in a record store
    • getRecord(): Used to retrieve a specific record from the record store
    • deleteRecord(): Used to remove a record from the record store

    The RecordStore object provides a convenient and flexible storage system for the MIDP platform. Using a RecordStore, an application can store information ranging from primitive types to complex serialized objects.

    RecordComparator
    The RMSStockStore object implements the javax.microedition.rms.RecordComparator interface. This interface is then passed to the RecordStore when it becomes necessary to sort the data being retrieved from the store. An object that implements the RecordComparator interface must implement the compare() method. The intent of this method is to determine the sorting order of two records in the database.

    In the implementation of compare that is contained within the RMSStockStore object, a comparison is being made between the stock symbols of two records in the database. Both records are converted from byte arrays to stock objects through the various InputStreams. The symbols of each stock are retrieved using the getSymbol() method and then compared using the String compareTo() method. That method returns three possible ints, corresponding to conditions of less than, equal to, or greater than.

        public int compare(byte[] rec1, byte[] rec2)
        {
        ...
        try {
            // Extract the stocks.
            Stock stock1 = StockStorage.readStock(inputStream1);
            Stock stock2 = StockStorage.readStock(inputStream2);

            String symbol1 = stock1.getSymbol();
            String symbol2 = stock2.getSymbol();

            compareValue = symbol1.compareTo(symbol2);
        }
        ...

    Based upon the value returned, different values are returned to the RecordStore so that it can determine how to organize the rows in the RecordEnumerator. You can use multiple RecordComparator objects if it is necessary to sort on different criteria for different queries.

        ...
        // Sort by symbol name
        if (compareValue < 0) {
            return RecordComparator.PRECEDES;
        }
        else if (compareValue > 0) {
            return RecordComparator.FOLLOWS;
        }
        else {
            return RecordComparator.EQUIVALENT;
        }
        }

    RecordFilter
    The RMSStockStore object implements the javax.microedition.rms.RecordFilter interface. This interface requires the implementation of a method called matches(). This method will act as a filter to enable the RecordStore to determine whether a given byte array matches the entity that is being queried in the tables. In this example, the RecordFilter is used to obtain the values of a single stock from the database. So, the matches() method must be used to ensure that only the records that match the stock are shown to be true by this method.

    A review of the following method demonstrates that the stock is again read from the InputStream using the StockStorage() method. The name of the stock is retrieved from the object using the getSymbol() method. Notice that the return statement itself performs the comparison between the name of the stock and the filter value that is stored as a class variable.

        public boolean matches(byte[] candidate)
        throws IllegalArgumentException
        {
            // If no filter set, nothing can match it.
            if (this.symbolFilter == null) {
                return false;
            }

            ByteArrayInputStream bais = new ByteArrayInputStream(candidate);
            DataInputStream inputStream = new DataInputStream(bais);
            String name = null;

            try {

                Stock stock = StockStorage.readStock(inputStream);
                int shares = stock.getNumShares();
                name = stock.getSymbol();
            }
            ...
            return (this.symbolFilter.equals(name));
        }

    Only records for which this method returns true will appear in the set of results to this lookup.

    StockDatabase
    The RMSStockStore object implements the StockDatabase interface defined for this example. These methods use the helper methods and the StockStorage object to perform the actions corresponding to their names.

    The buyStock() method is used to add a stock to the RecordStore. It is called from the BuyStockScreen when a user adds a stock to a portfolio.

        public void buyStock(String symbol, String shares, int price) throws StockException
        {
            int numShares = 0;

            try
            {
                numShares = Integer.parseInt( shares);
            }
            catch( NumberFormatException nfe)
            {
                throw new StockException("Number of shares value is invalid", nfe);
            }

    The buyStock() method performs some basic field validation to ensure that the values are present and of the correct format.

            if ( numShares <= 0 || price <= 0 || symbol.length() == 0)
            {
                throw new StockException("Invalid Data passed to RMS", null);
            }

    This example limits the user to a single entry in a portfolio for a given stock. To do so, the buyStock() method must look up to determine whether a given stock already exists in the portfolio.

            // check if there are existing shares
            Stock stock = getStock( symbol);

    If not, it will just add the stock to the portfolio.

            if ( stock == null)
            {
                // add the new shares
                addStock( numShares, symbol, price);
            }

    If the stock does exist, the first entry will be removed from the database and a new record inserted in its place. Note that you can use the RecordStore.setRecord() method instead of performing these two steps. I made this design choice to simplify this example.

            else
            {
                sellStock( stock, stock.getNumShares() );

                addStock( numShares + stock.getNumShares(), symbol, price);
            }

    The sellStock() method is called from both the buyStock() method and the SellStockScreen. This method is used to remove a stock from the data store.

        public void sellStock(Stock stock, int numShares) throws StockException

    The symbolFilter value is set to be the symbol of the stock to be sold. Recall that the RecordComparator uses this value in its matches() method to determine which records meet that criterion.

            // delete entry from the database
            this.symbolFilter = stock.getSymbol();

            Stock retrievedStock = null;

    The RecordStore provides a query mechanism through the user of the RecordFilter interface. To obtain an enumeration of records, pass the RecordFilter instance to the enumerateRecords() method. The filter will ensure that only records that contain this symbol will be returned. By passing the RecordComparator, the RecordStore will return the records according to the desired sort order. In this example, the RMSRecordStore implements the RecordComparator and RecordFilter interfaces, so they are passed in as this.

            try {
                RecordEnumeration re = recordStore.enumerateRecords(this, this, true);

                while(re.hasNextElement())
                {
                    int id = re.nextRecordId();
                    ByteArrayInputStream bais =
                       new ByteArrayInputStream(recordStore.getRecord(id));
                    DataInputStream inputStream = new DataInputStream(bais);
                    try {
                        retrievedStock = StockStorage.readStock(inputStream);

    The record is removed from the record store based upon its id value.

                        recordStore.deleteRecord( id);
                        ...

    The getStocks() method is used to obtain a list of stocks that are listed in the user's portfolio. This method will return a Vector of Stock objects that is used by the SelectUserScreen to retrieve the stock list. Notice that the value of the RecordFilter is passed in as null. This ensures that all records in the database will be returned and that no filtering will be used.

        public Vector getStocks() throws StockException
        {
            Vector stockVector = new Vector();

            try {
                // Enumerate the records using the comparator implemented
                // above to sort by game symbol.
                RecordEnumeration re = recordStore.enumerateRecords(null, this,
                                        true);
                stockVector = getStockHelper(re);
            }
            catch (RecordStoreException rse) {
                System.out.println(rse);
                rse.printStackTrace();
            }

    If the Vector is empty, throw an exception because this method should not be accessed unless the user has bought a stock previously.

            if ( stockVector.size() == 0)
                throw new StockException("No Stocks found in portfolio.");

            return stockVector;
        }

    StockStorage
    The StockStorage object is intended to encapsulate the storage of the Stock object to a single class. All other classes may retrieve and store the Stock object in the RecordStore without knowing the format of the data stored. The RecordStore itself has no working knowledge of the format of the Stock object in the database. If you decided that the date of purchase should be stored, you make that design (without any modification to the remainder of the application) by simply changing the fields in the StockStorage object.

    The Stock object is stored as follows:

    Table 3. Storing of the Stock object
    Field Description
    Number of shares Integer field
    Symbol UTF
    Cost Integer field

    By isolating the storage information to this object, the rest of the application is spared all knowledge of the lowest level of details.

    The writeStock() method will retrieve the values from the Stock object and pass them to the OutputStream.

        public static void writeStock(Stock stock, DataOutputStream outputStream)
           throws IOException
        {
            // Push the number of shares into a byte array.
            outputStream.writeInt( stock.getNumShares() );

            // Then push the stock name.
            outputStream.writeUTF( stock.getSymbol() );

            // push the stock price
            outputStream.writeInt( stock.getPrice() );
        }

    The readStock() method will retrieve values from the InputStream and create a new Stock object for the application to manipulate as needed.

        public static Stock readStock(DataInputStream inputStream) throws IOException
        {
            // read the number of shares
            int numShares = inputStream.readInt();

            // read the stock symbol
            String symbol = inputStream.readUTF();

            // read the stock price
            int price = inputStream.readInt();

            // return a new Stock object
            return new Stock( symbol, numShares, price);
        }

    Conclusion
    The intent of this articl