
 J 2 M E    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC)
Java(tm) 2 Platform, Micro Edition (J2ME(tm)) 
Tech Tips, for June 22, 2001. This issue covers:

     * Using the MIDP List Component
     * Enumerating, Filtering, and Sorting MIDP Record Stores
     
The J2ME Tech Tips are written by Eric Giguere
(http://www.ericgiguere.com), an engineer at iAnywhere 
Solutions, inc. Eric is the author of the book "Java 2 Micro
Edition: Professional Developer's Guide" and co-author of the 
upcoming "Mobile Information Device Profile for Java 2 Micro 
Edition," both books in John Wiley & Sons' Professional 
Developer's Guide series."

You can view this issue of the J2ME Tech Tips on the Web at
http://java.sun.com/jdc/J2METechTips/2001/tt0622.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING THE MIDP LIST COMPONENT

The Mobile Information Device Profile (MIDP) groups its user 
interface components into two sets of classes: the high-level 
API and the low-level API. With the high-level API you define 
your user interface using a set of platform-independent 
abstractions and let the MIDP implementation decide the look 
and behavior of the user interface. The low-level API gives you
complete control (and responsibility) of the drawing area and the 
processing of the raw input events. The low-level API was covered 
in the J2ME Tech Tip for March 19, 2001 "Using the MIDP Low-Level 
User Interface API" 
(http://java.sun.com/jdc/J2METechTips/2001/tt0319.html#tip2).  

This Tech Tip examines the List component, part of the 
high-level API. All MIDP user interface components are defined 
in the javax.microedition.lcdui package.

The List component displays a set of selectable strings. An 
optional image can also be displayed along with each string. 
The implementation displays the strings in some kind of list -- 
the exact format of the display is undefined -- and provides 
a way for the user to navigate from one item in the list to 
another, selecting or deselecting items as appropriate. The 
navigation and selection procedures are separate. In other words, 
the user can navigate to a specific item without selecting or 
deselecting it. This is referred to as moving the input focus.

The List class extends Screen, which is the base class for all 
top-level windows in the high-level API. Only one screen is 
ever visible at a time. A screen has an optional title. 
Because the Screen class extends Displayable, Command objects
can also be associated with List components. The List class also 
implements the Choice interface, which defines a number of 
methods that the List component has in common with another 
component of the high-level API: the ChoiceGroup component.

A List component supports three modes of item selection. The 
first mode, List.EXCLUSIVE, lets the user select exactly one item 
from the list. If the user selects an item and another item is 
already selected, the previous item is automatically deselected.  
Conceptually, it's as if the list was composed of a set of radio 
buttons, and implementations may in fact display it that way. The 
second mode, List.MULTIPLE, lets the user select zero or more of 
the items simultaneously. It's as if the list was composed of a 
set of checkboxes. The final mode, List.IMPLICIT, is like the 
exclusive mode, but the act of selecting an item also triggers an 
event, as if the user had triggered a command (more on this 
below).

The List class defines two constructors. Both constructors take 
the screen title and the list mode (one of List.EXCLUSIVE,
List.MULTIPLE, or List.IMPLICIT). The second constructor also
takes an array of strings and an array of images in order to
initialize the List. However, the initialization can be done
separately. For example, here's one way to construct a list
of items:

List l = new List( "Choose fruit:", List.EXCLUSIVE );
l.append( "orange", null );
l.append( "apple", null );
l.append( "pear", null );

The alternative is to use the array form of the constructor:

String[] fruitNames = { "orange", "apple", "pear" };

List l = new List( "Choose fruit:", List.EXCLUSIVE,
                   fruitNames, null );

Note that images are entirely optional. Even if you specify them, 
the implementation might ignore them. So it's safe to pass in 
null whenever an image or array of images is requested.

Before displaying a List component, you'll want to register
a command listener for it:

List l = ....;
CommandListener listener = ....; // often "this"

l.setCommandListener( listener );

A command listener is an instance of any class that implements 
the CommandListener interface. Often the MIDlet's main class is 
the listener. Only one listener can be registered at a time.  
A List in EXCLUSIVE or MULTIPLE mode must also register at least 
one Command object, otherwise no events will be sent to the 
command listener. This is because the EXCLUSIVE and MULTIPLE 
modes do not trigger any events as the user interacts with the 
list.

It's usually a good idea to add commands to lists in IMPLICIT 
mode as well as in EXCLUSIVE or MULTIPLE mode. When the user 
selects an item from an IMPLICIT mode list, the list notifies
its command listener using the special Command object defined as 
List.SELECT_COMMAND. This object gets passed to the listener's 
commandAction method as the first argument whenever a new item is 
selected. In other words, you check for implicit selection using 
code like this:

public void commandAction( Command c, Displayable d ){
    if( c == List.SELECT_COMMAND ){
        // implicit selection...
    } else if( ..... ){
        ..... // etc. etc.
    }
}

Make sure that any operation performed in response to
SELECT_COMMAND is intuitively obvious to the user. For example,
a List component that displays a list of emails might respond to
SELECT_COMMAND by displaying the text of the selected email
message. Other mail management operations are accessed by 
registering appropriate Command objects.

The List contents, that is, the text and images displayed by the 
List, can be changed at any time. List defines append, delete,
insert, and set methods for this purpose. A common requirement,
for example, is to delete the contents of a list. You can do this 
easily, like this:

public static void deleteListContents( List l ){
    int n = l.size();
    while( n-- > 0 ){
        l.delete( n );
    }
}

The size method returns the number of items currently stored in 
the List.  

If you find yourself doing these kinds of operations repeatedly, 
you might want to extend List to augment it with your own 
features, as in the following:

public class ExtendedList extends List {
    public ExtendedList( String title, int mode ){
        super( title, mode );
    }
    
    public ExtendedList( String title, int mode, 
                         String[] itemText, Image[] itemImages ){
        super( title, mode, itemText, itemImages );
    }
    
    public void deleteAll(){
        int n = size();
        while( n-- > 0 ){
            delete( n );
        }
    }
}

After a List has been constructed, you can obtain the index of the 
selected item at any time. In EXCLUSIVE or IMPLICIT mode, use 
getSelectedIndex, which returns the index (starting at 0) of the 
selected item:

    List l = ....; // some list
    int  which = l.getSelectedIndex();
    
Lists in MULTIPLE mode return -1 for getSelectedIndex, because
more than one item can be selected at any given time. Use
getSelectedFlags to return the selected state of each item into an 
array you supply:

    List      l = ....; // some list
    boolean[] selected = new boolean[ l.size() ];
    
    l.getSelectedFlags( selected );
    for( int i = 0; i < selected.length; ++i ){
        if( selected[i] ){
            System.out.println( "Item " + i + " is selected!" );
        }
    }
    
You can also check the selected state of an individual item at 
any time by calling isSelected. You can set its state by calling 
setSelectedFlags or setSelectedIndex. For example, here's how to 
toggle the selected items in a MULTIPLE mode List:

public void toggleItems( List l ){
    boolean[] selected = new boolean[ l.size() ];
    l.getSelectedFlags( selected );
    for( int i = 0; i < selected.length; ++i ){
        selected[i] = !selected[i];
    }
    l.setSelectedFlags( selected );
}

Another way to toggle the selected items is to set each item's 
selected state individually:

public void toggleItems( List l ){
    int n = l.size();
    for( int i = 0; i < n; ++i ){
        l.setSelectedIndex( i, !l.isSelected( i ) );
    }
}

The latter method might cause excessive repainting of the list, 
however, and should be avoided in favor of calling 
setSelectedFlags.

Each item in a List can have an associated image. An image is an 
instance of the Image class, usually obtained by calling 
Image.createImage, and passing it the path of an image in the 
MIDlet suite's JAR file. For example:

Image checked = null;
Image unchecked = null;

try {
    checked = Image.createImage( "/images/check.png" );
    unchecked = Image.createImage( "/images/unchecked.png" );
}
catch( java.io.IOException e ){
}
    
You can also create images dynamically by using the Image class's 
offscreen buffer capability. However, any image used in a List or 
other high-level UI component must be an immutable, that is, it
must be an unchangeable image. (See the Image class documentation 
for information on how to do this with dynamically-generated 
images.)

Once you have an image or set of images, you can assign them to 
the items either in the constructor or as arguments to the append, 
insert or set methods. You assign the items in the constructor
by passing in an array of images equal in length to the array of 
strings that define the items. Here's an example that illustrates
assigning images to the append method:

List l = ....; // some list

try {
    l.append( "orange", Image.createImage( "/orange.png" ) );
    l.append( "apple", Image.createImage( "/apple.png" ) );
    l.append( "pear", Image.createImage( "/pear.png" ) );
}
catch( IOException e ){
}
   
Keep the images as small as possible, no more than 10-to-16 
pixels high. Make them all the same size to ensure that the text 
is painted correctly. Don't depend on the images being there, 
however, and make sure the item text is descriptive enough without 
the image.

Let's end the tip with a simple MIDlet that demonstrates the use 
of the List component.  

import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class ListDemo extends MIDlet {

    private Display              display;
    private int                  mode = List.IMPLICIT;

    private Command exitCommand = new Command( "Exit",
                                             Command.SCREEN, 2 );
    private Command selectCommand = new Command( "Select",
                                                 Command.OK, 1 );
    private Command nextCommand = new Command( "Next",
                                             Command.SCREEN, 2 );
    public ListDemo(){
    }

    protected void destroyApp( boolean unconditional )
                       throws MIDletStateChangeException {
        exitMIDlet();
    }

    protected void pauseApp(){
    }

    protected void startApp() throws MIDletStateChangeException {
        if( display == null ){ // first time called...
            initMIDlet();
        }
    }

    private void initMIDlet(){
        display = Display.getDisplay( this );
        display.setCurrent( new SampleList( mode ) );
    }

    public void exitMIDlet(){
        notifyDestroyed();
    }

    public static final String[] items = {
        "First", "Second", "Third", "Fourth"
    };

    class SampleList extends List implements CommandListener {

        private int mode;

        SampleList( int mode ){
            super( "", mode, items, null );
            addCommand( exitCommand );
            addCommand( selectCommand );
            addCommand( nextCommand );
            setCommandListener( this );

            switch( mode ){
                case IMPLICIT:
                    setTitle( "Implicit" );
                    break;
                case EXCLUSIVE:
                    setTitle( "Exclusive" );
                    break;
                case MULTIPLE:
                    setTitle( "Multiple" );
                    break;
            }

            this.mode = mode;
        }

        public void commandAction( Command c, Displayable d ){
            if( c == exitCommand ){
                exitMIDlet();
            } else if( c == selectCommand ){
                showSelection( false );
            } else if( c == SELECT_COMMAND ){
                showSelection( true );
            } else if( c == nextCommand ){
                if( mode == List.IMPLICIT ){
                    mode = List.EXCLUSIVE;
                } else if( mode == List.EXCLUSIVE ){
                    mode = List.MULTIPLE;
                } else {
                    mode = List.IMPLICIT;
                }
    
                display.setCurrent( new SampleList( mode ) );
            }
        }

        private void showSelection( boolean implicit ){
            Alert alert = new Alert( 
                                   implicit ? "Implicit Selection"
                                         : "Explicit Selection" );
            StringBuffer buf = new StringBuffer();

            if( mode == MULTIPLE ){
                boolean[] selected = new boolean[ size() ];
                getSelectedFlags( selected );

                for( int i = 0; i < selected.length; ++i ){
                    if( selected[i] ){
                        if( buf.length() == 0 ){
                            buf.append( "You selected: " );
                        } else {
                            buf.append( ", " );
                        }

                        buf.append( getString( i ) );
                    }
                }

                if( buf.length() == 0 ){
                    buf.append( "No items are selected." );
                }
            } else {
                buf.append( "You selected " );
                buf.append( getString( getSelectedIndex() ) );
            }

            alert.setString( buf.toString() );
            alert.setTimeout( Alert.FOREVER );

            display.setCurrent( alert, display.getCurrent() );
        }
    }
}


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
ENUMERATING, FILTERING, AND SORTING MIDP RECORD STORES

The Record Management System (RMS), part of the Mobile
Information Device Profile (MIDP), lets you store arbitrary byte 
arrays as records in a database. The set of records is referred 
to as a record store. The RMS assigns a record ID to each record 
when it is created. The record ID is an integer greater than or 
equal to 1. The application uses this ID to get and set the 
content of a record.

Record IDs are unique within a record store. In other words, 
after a record ID has been assigned to a record, the record ID
is not reused, even if that record is deleted. This makes it easy 
to track individual records. It does, however, pose a problem: 
how do you get a list of all the records in a record store, given 
that they aren't necessarily in a continuous, sequential order?  
The answer is to use an enumeration.

The RecordStore class defines an enumerateRecords method that 
returns a RecordEnumeration. RecordEnumeration is an interface 
whose purpose is similar to the java.util.Enumeration interface,
but with more capabilities. (The RecordEnumeration interface,
like all RMS classes and interfaces, is defined in the 
javax.microedition.rms package). Here is the definition for
RecordEnumaration:

public interface RecordEnumeration {
    void destroy();
    boolean hasNextElement();
    boolean hasPreviousElement();
    boolean isKeptUpdated();
    void keepUpdated( boolean keepUpdated );
    byte[] nextRecord() throws InvalidRecordIDException, 
                    RecordStoreNotOpenException, 
                    RecordStoreException;
    int nextRecordId() throws InvalidRecordIDException;
    int numRecords();
    byte[] previousRecord() throws InvalidRecordIDException, 
                    RecordStoreNotOpenException, 
                    RecordStoreException;
    int previousRecordId() throws InvalidRecordIDException;
    void rebuild();
    void reset();
}

RecordEnumeration lets you traverse through a set of records.  
Unlike Enumeration, however, RecordEnumeration allows for 
bidirectional traversal of that set -- you can go backwards 
through the set of records as well as forwards. The set of 
records to traverse is defined by the enumerateRecords method.

To iterate over the entire set of records in a record store,
do the following:

RecordStore rs = .....; // an open record store
RecordEnumeration enum = null;

try {
    enum = rs.enumerateRecords( null, null, false );
    while( enum.hasMoreElements() ){
        int id = enum.getNextRecordId();
        // do something here with the record
    }
}
catch( RecordStoreException e ){
}
finally {
    enum.destroy();
}

The first two arguments to the enumerateRecords method are 
used to filter and sort the records in an enumeration. (This tip
will discuss filtering and sorting shortly.) The last argument 
determines whether the enumeration tracks changes to the record 
store. If the last argument is true, the enumeration updates 
itself as records are added to or deleted from a record store.  
This can be an expensive operation, however, and for most 
applications tracking is not required. Note that enumerateRecords
throws RecordStoreNotOpenException if the record store is not 
open. RecordStoreNotOpenExceptio is a subclass of 
RecordStoreException.
 
RecordEnumeration defines two methods to traverse forward through 
the enumeration: getNextRecordId and getNextRecord. It also
defines two methods to traverse backwards: getPreviousRecordId 
and getPreviousRecord. Each method moves to the next or previous 
record in the enumeration, returning either the ID of the record 
(getNextRecordId and getPreviousRecordId) or its contents 
(getNextRecord and getPreviousRecord). An application calls 
hasNextElement or hasPreviousElement to determine if there 
are any more records to traverse in a specific direction. 

You can reset the enumeration at any time by calling reset.

When an application is done with an enumeration it must call the 
enumeration's destroy method. This frees up any resources used by 
the enumeration.

If the first two arguments to enumerateRecords are null, all the 
records in the record store are returned in an undefined order. To 
return a subset of the records or to return the records in a 
specific order, you must define a filter, a comparator, or both.
To do this, pass in objects that implement the RecordFilter or
RecordComparator interfaces, depending on whether you defined
a filter, comparator, or both.

The RecordFilter interface defines a single method:

public interface RecordFilter {
    boolean matches( byte[] recordData );
}

A filter is an instance of a class that implements the
RecordFilter interface. If a filter is defined in the call to 
enumerateRecords, the filter's matches method is called for each 
record in the record store. The matches method determines if the 
record is included in the enumeration. Here, for example, is a 
filter that filters out empty records or records whose first byte 
is set to zero:

public class MyFilter implements RecordFilter {
    public boolean matches( byte[] recordData ){
        return( recordData.length > 0 && recordData[0] != 0 );
    }
}

Use the filter like this:

enum = rs.enumerateRecords( new MyFilter(), null, false );

Note that the filter is passed the contents of a record,
but not the record ID or a reference to the record store itself.

The RecordComparator interface also defines a single method:

public interface RecordComparator {
    boolean compare( byte[] rec1, byte[] rec2 );
    
    int EQUIVALENT = 0;
    int FOLLOWS = 1;
    int PRECEDES = -1;
}

A comparator is an instance of a class that implements the
RecordComparator interface. If a comparator is defined in the 
call to enumerateRecords, the enumerator sorts the records in the 
set. The enumerator calls the comparator's compare method to 
determine the relative order of two records. The comparator 
returns EQUIVALENT if both records are equivalent, FOLLOWS if the 
first record follows the second record, or PREVIOUS is the first 
precedes the second record. Here is a comparator that sorts
records based on their length:

public class MyComparator implements RecordComparator {
    public boolean compare( byte[] rec1, byte[] rec2 ){
        if( rec1.length == rec2.length ){
            return EQUIVALENT;
        } else if( rec1.length < rec2.length ){
            return PRECEDES;
        } else {
            return FOLLOWS;
        }
    }
}

Of course, most comparators would compare records based on their 
contents. Use the comparator like this:

enum = rs.enumerateRecords( null, new MyComparator(), false );

Again, only the contents of the records to compare are passed to 
the comparator.

Both a filter and a comparator can be associated with an
enumeration. The filter is called first to determine which
records are in the enumeration. The comparator is then called to 
sort those records.

If tracking is enabled, filtering and sorting occurs whenever the 
record store is modified. This can be quite expensive to perform.  
You can enable or disable an enumeration's tracking of the 
underlying record store by using the keepUpdated method.

If tracking is disabled but you want to "refresh" the enumeration
from the current set of records in the record store, call
the rebuild method.

Let's end this tip with a simple MIDlet that demonstrates how to 
fill a List component from a record store. The records in the 
record store contain the first and last names of a group of 
people. The MIDlet includes a screen that lets you choose how to 
sort and filter the records.

import java.io.*;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.rms.*;

// Demonstrates simple record sorting and filtering

public class EnumDemo extends MIDlet
                      implements CommandListener {

    // Data members we need

    private Display              display;
    private RecordStore          rs;
    private EnumList             enumListScreen;
    private SortOptions          sortOptionsScreen;
    private String               firstName;
    private String               lastName;
    private byte[]               data = new byte[200];
    private ByteArrayInputStream bin = new ByteArrayInputStream( 
                                                          data );
    private DataInputStream      din = new DataInputStream( bin );

    // Define the four commands we use

    private Command exitCommand = new Command( "Exit",
                                               Command.EXIT, 1 );
    private Command sortCommand = new Command( "Sort",
                                               Command.SCREEN, 1 );
    private Command cancelCommand = new Command( "Cancel",
                                               Command.CANCEL, 1 );
    private Command okCommand = new Command( "OK", Command.OK, 1 );

    // Define the sorting modes

    private static final int SORT_NONE = 0;
    private static final int SORT_FIRST_LAST = 1;
    private static final int SORT_LAST_FIRST = 2;

    // Define the filtering modes

    private static final int FILTER_NONE = 0;
    private static final int FILTER_STARTSWITH = 1;
    private static final int FILTER_CONTAINS = 2;

    // A simple class to hold the parts of a record

    private static class Record {
        String firstName;
        String lastName;
    }

    // Precanned names

    private static final String[][] names = {
        { "Eric", "Giguere" },
        { "John", "Doe" },
        { "Lisa", "Munro" },
        { "Stephanie", "Kwaly" },
        { "Dave", "Boyer" },
        { "Gwen", "Stephens" },
        { "Jennifer", "Lopert" },
        { "Ed", "Ort" },
    };

    // Constructor

    public EnumDemo(){
    }

    // Clean up

    protected void destroyApp( boolean unconditional )
                       throws MIDletStateChangeException {
        exitMIDlet();
    }

    // Close the record store for now

    protected void pauseApp(){
        closeRecordStore();
    }

    // Initialize things and reopen record store if not open

    protected void startApp() throws MIDletStateChangeException {
        if( display == null ){ // first time called...
            initMIDlet();
        }

        if( rs == null ){
            openRecordStore();
        }
    }

    // Once-only initialization code

    private void initMIDlet(){
        display = Display.getDisplay( this );
        enumListScreen = new EnumList();
        sortOptionsScreen = new SortOptions();

        if( openRecordStore() ){
            enumListScreen.resort();
            display.setCurrent( enumListScreen );
        }
    }

    // Termination code

    public void exitMIDlet(){
        closeRecordStore();
        notifyDestroyed();
    }

    // Add a first-last name pair to the record store

    private void addName( String first, String last,
                          ByteArrayOutputStream bout,
                          DataOutputStream dout ){
        try {
            dout.writeUTF( first );
            dout.writeUTF( last );
            dout.flush();
            byte[] data = bout.toByteArray();
            rs.addRecord( data, 0, data.length );
            bout.reset();
        }
        catch( Exception e ){
        }
    }

    // Fill record store with some precanned names

    private void fillRecordStore(){
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream dout = new DataOutputStream( bout );

        for( int i = 0; i < names.length; ++i ){
            addName( names[i][0], names[i][1], bout, dout );
        }
    }

    // Open record store, if empty then fill it

    private boolean openRecordStore(){
        try {
            if( rs != null ) closeRecordStore();

            rs = RecordStore.openRecordStore( "EnumDemo", true );
            if( rs.getNumRecords() == 0 ){
                fillRecordStore();
            }
            return true;
        }
        catch( RecordStoreException e ){
            return false;
        }
    }

    // Close record store

    private void closeRecordStore(){
        if( rs != null ){
            try {
                rs.closeRecordStore();
            }
            catch( RecordStoreException e ){
            }

            rs = null;
        }
    }

    // Move to and read in a record

    private boolean readRecord( int id, Record r ){
        boolean ok = false;

        r.firstName = null;
        r.lastName = null;

        if( rs != null ){
            try {
                rs.getRecord( id, data, 0 );
                r.firstName = din.readUTF();
                r.lastName = din.readUTF();
                din.reset();

                ok = true;
            }
            catch( Exception e ){
            }
        }

        return ok;
    }

    // Event handling

    public void commandAction( Command c, Displayable d ){
        if( c == exitCommand ){
            exitMIDlet();
        } else if( c == sortCommand ){
            display.setCurrent( sortOptionsScreen );
        } else if( d == sortOptionsScreen ){
            if( c == okCommand ){
                enumListScreen.resort();
            }
            
            display.setCurrent( enumListScreen );
        }
    }

    // Main screen -- a list of names

    class EnumList extends List
                   implements RecordComparator, RecordFilter {
        private int    sortBy;
        private int    filterBy;
        private String filterText;
        private Record r1 = new Record();
        private Record r2 = new Record();

        // Constructor

        EnumList(){
            super( "Enum Demo", IMPLICIT );
            addCommand( exitCommand );
            addCommand( sortCommand );
            setCommandListener( EnumDemo.this );
        }

        // Resort the data and refill the list

        public void resort(){
            sortBy = sortOptionsScreen.getSortType();
            filterBy = sortOptionsScreen.getFilterType();
            filterText = sortOptionsScreen.getFilterText();

            RecordComparator comparator = null;
            RecordFilter     filter = null;

            if( sortBy != 0 ){
                comparator = this;
            }

            if( filterBy != 0 ){
                filter = this;
            }

            deleteAll();

            try {
                RecordEnumeration enum = rs.enumerateRecords( 
                                     filter, comparator, false );
                Record            r = new Record();

                while( enum.hasNextElement() ){
                    int id = enum.nextRecordId();
                    if( readRecord( id, r ) ){
                        if( sortBy == SORT_LAST_FIRST ){
                            append( r.lastName + ", " + 
                                             r.firstName, null );
                        } else {
                            append( r.firstName + " " + 
                                              r.lastName, null );
                        }
                    }
                }

                enum.destroy();
            }
            catch( RecordStoreException e ){
            }
        }

        // Delete all items in the list

        private void deleteAll(){
            int n = size();
            while( n > 0 ){
                delete( --n );
            }
        }

        // The comparator

        public int compare( byte[] rec1, byte[] rec2 ){
            try {
                ByteArrayInputStream  bin = 
                                new ByteArrayInputStream( rec1 );
                DataInputStream       din = 
                                      new DataInputStream( bin );

                r1.firstName = din.readUTF();
                r1.lastName = din.readUTF();

                bin = new ByteArrayInputStream( rec2 );
                din = new DataInputStream( bin );

                r2.firstName = din.readUTF();
                r2.lastName = din.readUTF();

                if( sortBy == SORT_FIRST_LAST ){
                    int cmp = r1.firstName.compareTo( 
                                                  r2.firstName );
                 System.out.println( r1.firstName + 
                  " compares to " + r2.firstName + 
                                               " gives " + cmp );
                    if( cmp != 0 ) return ( 
                                  cmp < 0 ? PRECEDES : FOLLOWS );
                    cmp = r2.lastName.compareTo( r2.lastName );
                    if( cmp != 0 ) return ( 
                                  cmp < 0 ? PRECEDES : FOLLOWS );
                } else if( sortBy == SORT_LAST_FIRST ){
                    int cmp = r1.lastName.compareTo( 
                                                   r2.lastName );
                    if( cmp != 0 ) return ( 
                                  cmp < 0 ? PRECEDES : FOLLOWS );
                    cmp = r2.firstName.compareTo( r2.firstName );
                    if( cmp != 0 ) return ( 
                                  cmp < 0 ? PRECEDES : FOLLOWS );
                }
            }
            catch( Exception e ){
            }

            return EQUIVALENT;
        }

        // The filter

        public boolean matches( byte[] rec ){
            try {
                ByteArrayInputStream  bin = 
                                 new ByteArrayInputStream( rec );
                DataInputStream       din = 
                                      new DataInputStream( bin );

                r1.firstName = din.readUTF();
                r1.lastName = din.readUTF();

                if( filterBy == FILTER_STARTSWITH ){
                    return( r1.firstName.startsWith( filterText ) 
                            ||
                          r1.lastName.startsWith( filterText ) );
                } else if( filterBy == FILTER_CONTAINS ){
                    return( r1.firstName.indexOf( filterText ) 
                            >= 0 ||
                        r1.lastName.indexOf( filterText ) >= 0 );
                }
            }
            catch( Exception e ){
            }

            return false;
        }
    }

    // The options screen for choosing which sort and filter modes
    // to use, including the filter text

    class SortOptions extends Form {

        // Constructor

        SortOptions(){
            super( "Sort Options" );
            addCommand( okCommand );
            addCommand( cancelCommand );
            setCommandListener( EnumDemo.this );

            append( sortTypes );
            append( filterTypes );
            append( filterText );
        }

        // Return current sort type

        public int getSortType(){
            return sortTypes.getSelectedIndex();
        }

        // Return current filter type

        public int getFilterType(){
            return filterTypes.getSelectedIndex();
        }

        // Return current filter text

        public String getFilterText(){
            return filterText.getString();
        }

        // Labels and user interface components

        private String[] sortLabels = 
                         { "None", "First Last", "Last, First" };

        private String[] filterLabels = 
                           { "None", "Starts With", "Contains" };

        private ChoiceGroup sortTypes = new ChoiceGroup( 
                                                    "Sort Type:",
                                           ChoiceGroup.EXCLUSIVE,
                                              sortLabels, null );

        private ChoiceGroup filterTypes = new ChoiceGroup( 
                                                  "Filter Type:",
                                           ChoiceGroup.EXCLUSIVE,
                                            filterLabels, null );

        private TextField filterText = new TextField( 
                                                  "Filter Text:",
                                                   null, 20, 0 );
    }
}
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE

Sun respects your online time and privacy. The Java Developer 
Connection mailing lists are used for internal Sun 
Microsystems(tm) purposes only. You have received this email 
because you elected to subscribe. To unsubscribe, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
uncheck the appropriate checkbox, and click the Update button.

As of May  22, 2001, Sun Microsystems updated its Privacy Policy 
(http://sun.com/privacy) to give you a better understanding of 
Sun's Privacy Policy and Practice. If you have any questions, 
contact privacy@sun.com.

- SUBSCRIBE

To subscribe to a JDC newsletter mailing list, go to the 
Subscriptions page (http://developer.java.sun.com/subscription/), 
choose the newsletters you want to subscribe to, and click Update.


- FEEDBACK
Comments? Send your feedback on the J2ME Tech Tips to:

jdc-webmaster@sun.com


- ARCHIVES
You'll find the J2ME Tech Tips archives at:

http://java.sun.com/jdc/J2METechTips/index.html

- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, 
see:

http://java.sun.com/jdc/copyright.html


- LINKS TO NON-SUN SITES
The J2ME Tech Tips may provide, or third parties may provide, 
links to other Internet sites or resources. Because Sun has no 
control over such sites and resources, You acknowledge and 
agree that Sun is not responsible for the availability of such 
external sites or resources, and does not endorse and is not 
responsible or liable for any Content, advertising, products, 
or other materials on or available from such sites or resources. 
Sun will not be responsible or liable, directly or indirectly, 
for any damage or loss caused or alleged to be caused by or in 
connection with use of or reliance on any such Content, goods or 
services available on or through any such site or resource.

J2ME Tech Tips 
June 22, 2001

Sun, Sun Microsystems, Java, Java Developer Connection, and 
J2ME and are trademarks or registered trademarks of 
Sun Microsystems, Inc. in the United States and other countries.




