
 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 March 19, 2001. This issue covers:

         * Using Record Stores Efficiently
         * Using the MIDP Low-Level User Interface API
         
The J2ME Tech Tips are written by Eric Giguere
(http://www.ericgiguere.com), an engineer at iAnywhere 
Solutions, inc, and author of the book "Java 2 Micro
Edition: Professional Developer's Guide."

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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING RECORD STORES EFFICIENTLY

The February 20, 2001 J2ME Tech Tip "Record Management System 
Basics" introduced the Record Management System (RMS), a set of 
classes for persistently storing and retrieving data. RMS is
defined in the Mobile Information Device Profile (MIDP). If you
read the tip, you discovered how simple it is to create and use 
record stores. This tip focuses on getting data into and out of 
a record store as quickly and efficiently as possible.

The data in a record store is stored in chunks called records.  
A record is a set of arbitrary binary data, basically an array 
of bytes. You always read or write an entire record at once, 
using the getRecord, addRecord and setRecord methods of the 
RecordStore class. Here's the general pattern:

RecordStore rs = null;
byte[]      data;
int         id = ....; // assign a record ID

try {
    rs = RecordStore.openRecordStore( "mydata", false );
    data = rs.getRecord( id );
    
    ..... // modify the data
    
    rs.setRecord( id, data, 0, data.length ); 
    rs.closeRecordStore();
}
catch( Exception e ){
    // do something here
}


There's no way to change just part of a record: you must read in 
the entire record, make the change to the data in memory, and 
then write the entire record back to the record store.

This brings up a good rule regarding using data stores
efficiently: minimize the number of times you read or write data 
to a record store. At the very least, minimize the number of 
writes; on some devices a write operation is much more expensive 
than a read operation. Palm devices, for example, write-protect 
most of their memory to prevent applications from corrupting each
other's data. In such an environment, the fewer writes you do, 
the faster the application runs.

Even if you limit the number of times you write data back to a 
record store, you might find that reading the data becomes a 
bottleneck in your application. This is especially true when
enumerating the records. Part of the problem often lies in the 
excessive object "churning" that occurs. Churning refers to the 
act of continually creating and discarding objects (including
arrays) from the memory heap. Not only is object creation 
expensive as far as time, but churning can also overwhelm the 
garbage collector.

Here, for example, is one way of cycling through all the records 
in a record store:

RecordStore rs = ....; // an open record store

try {
    int    lastID = rs.getNextRecordID();
    byte[] data;
   
    for( int i = 0; i < lastID; ++i ){
        try {
            data = rs.getRecord( i );
            .... // do something with the data
        }
        catch( InvalidRecordIDException e ){
            continue;
        }
    }
}
catch( Exception e ){
    // error
}

Another way is to use an enumeration:

RecordStore rs = ....; // an open record store

try {
    RecordEnumeration enum = rs.enumerateRecords( 
                                   null, null, false );
    while( enum.hasNextElement() ){
        byte[] data = enum.nextRecord();
        .... // do something with the data
    }
}
catch( Exception e ){
    // error
}

The problem with both approaches is that they allocate new array 
objects for each record, a classic case of object churning. A 
better approach is to reuse the same array, discarding it only 
when it's too small. For example:

RecordStore rs = ....; // an open record store

try {
    RecordEnumeration enum = rs.enumerateRecords( 
                                   null, null, false );
    byte[]            data = new byte[100];
    int               len = 0;
    
    while( enum.hasNextElement() ){
        int id = enum.nextRecordId();

        len = rs.getRecordSize( id );
        if( len > data.length ){
        // add a growth factor
           data = new byte[ len + 40 ]; 
        }
        rs.getRecord( id, data, 0 );

        // do something with the data
    }
}
catch( Exception e ){
    // error
}

Notice how the array automatically grows to accommodate the size 
of a record. Of course, if each record is the same (fixed) size, 
then you don't even have to worry about growing the array.

The second rule is when reading records, reuse byte arrays 
if possible. The same rule about reusing byte arrays also holds
for writing records. However, object churning isn't always a 
problem when writing data because records tend to be read more 
than they're written. 

Reusing the same array isn't usually enough to avoid object 
churning. This is because the recommended way to read the data 
is to use a DataInputStream object, as in the following example:

byte[] data = .....; // data from a record store

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

String  name = din.readUTF();
boolean isFemale = din.readBoolean();
// etc. etc.

If you create a new set of input streams for each record you 
read, then you're still creating and discarding too many objects.  
However, if you reuse the raw byte array, you can also reuse the 
input streams. You can do this by using the reset method to make 
the streams read from the beginning of the array. For example,
consider reading a set of records, where each record consists of 
a boolean value and two integer values:

RecordStoreEnumeration enum = ...; // get a record enumeration
byte[]                 data = new byte[9]; // record size
ByteArrayInputStream   bin = new ByteArrayInputStream( data );
DataInputStream        din = new DataInputStream( bin );

while( enum.hasNextElement() ){
    int id = enum.nextRecordId();
    getRecord( id, data, 0 );
    din.reset(); // move stream back to start
    
    boolean first = din.readBoolean();
    int     second = din.readInt();
    int     third = din.readInt();
     
    // do something here
}

This is a more efficient way to read records. So the third rule
of efficient record usage is: reuse the input streams whenever 
you reuse the raw byte array.

Let's wrap up the discussion of efficient record usage by writing 
a simple Record class that you can use to read records:

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

public class Record implements DataInput {

    private RecordStore _rs;
    private byte[] _data;
    private int _length;
    private int _id;
    private DataInputStream _din;

    public Record( RecordStore rs ){
        this( rs, 100 );
    }

    public Record( 
               RecordStore rs, int initialRecordSize ){
        _rs     = rs;
        _data   = new byte[ initialRecordSize ];
        _din    = new DataInputStream( 
                   new ByteArrayInputStream( _data ) );
        _length = -1;
    }

    public byte[] getByteArray() { return _data; }

    public int getLength() { return _length; }

    public byte[] moveTo( int id ) 
                    throws RecordStoreNotOpenException,
                              InvalidRecordIDException,
                                  RecordStoreException,
                                           IOException
    {
        _length = _rs.getRecordSize( id );

        if( _length > _data.length ){
            _data = new byte[ _length + 40 ];
            _din  = new DataInputStream( 
                   new ByteArrayInputStream( _data ) );
        }

        _rs.getRecord( id, _data, 0 );
        _id = id;
        _din.reset();

        return _data;
    }

    public void readFully(byte b[]) 
                                   throws IOException {
        _din.readFully( b );
    }

    public void readFully(byte b[], int off, int len) 
                                   throws IOException {
        _din.readFully( b, off, len );
    }

    public int skipBytes(int n) throws IOException {
        return _din.skipBytes( n );
    }

    public boolean readBoolean() throws IOException {
        return _din.readBoolean();
    }
 
    public byte readByte() throws IOException {
        return _din.readByte();
    }

    public int readUnsignedByte() 
                                   throws IOException {
        return _din.readUnsignedByte();
    }

    public short readShort() throws IOException {
        return _din.readShort();
    }

    public int readUnsignedShort() 
                                   throws IOException {
        return _din.readUnsignedShort();
    }

    public char readChar() throws IOException {
        return _din.readChar();
    }

    public int readInt() throws IOException {
        return _din.readInt();
    }

    public long readLong() throws IOException {
        return _din.readLong();
    }

    public String readUTF() throws IOException {
        return _din.readUTF();
    }
}

You then use the class like this:

try {
    rs = RecordStore.openRecordStore( "mydata", true );
  
    // Write two records to the record store

    ByteArrayOutputStream bout = 
                           new ByteArrayOutputStream();
    DataOutputStream dout = 
                          new DataOutputStream( bout );
    byte[] data;

    dout.writeUTF( "this is a test" );
    dout.writeInt( 1 );
    dout.flush();
    data = bout.toByteArray();

    rs.addRecord( data, 0, data.length );

    bout.reset();
    dout.writeUTF( "this is another test" );
    dout.writeInt( 99 );
    dout.flush();
    data = bout.toByteArray();

    rs.addRecord( data, 0, data.length );
   
    // Now read through the record store

    Record record = new Record( rs );
    int lastID = rs.getNextRecordID();
    RecordEnumeration enum = rs.enumerateRecords( 
                                   null, null, false );
    while( enum.hasNextElement() ){
        int id = enum.nextRecordId();
        record.moveTo( id );
        System.out.println( record.readUTF() + " " + 
                                    record.readInt() );
    }

    rs.closeRecordStore();
}
catch( Exception e ){
    // handle error
}
    
You could easily extend the Record class to include methods such
as getFirstName and getLastName to make it easy to extract the
data you've stored in a record.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
USING THE MIDP LOW-LEVEL USER INTERFACE API

With J2ME, profiles are responsible for defining the user 
interface (UI) application programming interface (API). The 
Mobile Information Device Profile (MIDP) defines two such APIs, 
referred to as the high-level and low-level APIs. The high-level
API requires you to use task-oriented abstractions to define what 
the user interface does. You have no real control over what gets 
drawn on the screen -- the implementation selects the best 
approach for the device. The high-level API is portable across
all MIDP-enabled devices and is really geared towards business 
applications.  Look for more information about the high-level API
in a future J2ME Tech Tip.

The low-level API is aimed squarely at game developers. Unlike 
the high-level API, the low-level API gives you complete access
to the screen and to input events. However, this access comes
with a price, because you're then responsible for drawing 
everything that is shown on the screen. You can use both the 
high-level and low-level APIs in the same application, but not at 
the same time. Think of the application as a deck of cards, with 
only one card visible at a time (much like the behavior provided 
by the java.awt.CardLayout class in the J2SE(tm) Platform).
Each card, known as a screen in MIDP terminology, in the deck can 
use either the high-level API or the low-level API, but not both.  
The only exception applies to the use of command objects, 
discussed later in this tip.

To use the low-level API in a MIDlet, you must write a class 
that extends the Canvas class:

// Simple canvas

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

public class MyCanvas extends Canvas {

    private MIDlet midlet;

    public MyCanvas( MIDlet midlet ){
        this.midlet = midlet;
    } 
 
    protected void paint( Graphics g ){
        g.setColor( 255, 255, 255 );
        g.fillRect( 0, 0, getWidth(), getHeight() );
        g.setColor( 0, 0, 0 );
        g.drawString( "Hello there!", getWidth()/2, 0,
                       g.TOP | g.HCENTER );
    }
}

All user interface classes are in the javax.microedition.lcdui 
package. Notice that you also need to import the 
javax.microedition.midlet package because you will pass to each 
canvas a reference to the MIDlet. Your canvas subclass must 
implement a paint method, which is called by the system to 
repaint the screen. The paint method is passed a Graphics object
which defines methods for all the standard drawing primitives 
you would expect, such as drawArc, drawLine, drawRect, and
drawString. Color is supported in the API using a 24-bit RGB 
model, although the device might support less color than the API.  
The MyCanvas example simply erases the screen by painting it
white and then draws a string (in black) at the center top of the 
screen.

You activate a canvas by calling the setCurrent method on the 
MIDlet's Display object, usually in the startApp method of the 
application's MIDlet class:

// Simple MIDlet

import javax.microedition.midlet.*;

public class MyMIDlet extends MIDlet {

    private Display  display;
    private MyCanvas canvas;
   
    public MyMIDlet(){
        display = Display.getDisplay( this );
        canvas  = new MyCanvas( this );
    }
   
    protected void startApp(){
        display.setCurrent( canvas );
    }
   
    protected void pauseApp(){
    }
  
    protected void destroyApp( boolean unconditional ){
    }
   
    public void exit(){
        destroyApp( true );
        notifyDestroyed();
    }
}

Although this MIDlet works, it has one problem: there is no 
obvious way to exit from it. You need to trap user input in some 
way. There are two ways to do this: using raw input events or 
command events.

A canvas receives raw input events by overriding the appropriate 
event delivery method defined by the Canvas class. There are 
methods for events generated by:

o Pressing the keypad (keyPressed, keyRepeated, and keyReleased)
o Using the pointer (pointerPressed, pointerDragged and 
  pointerReleased) if a pointer is available on the device
o Showing the canvas (showNotify, hideNotify).  

For example, you could add a way to terminate the application by 
defining a keyPressed event on the canvas:

protected void keyPressed( int keyCode ){
    ((MyMIDlet) midlet).exit();
}

In all keypad events, the keyCode identifies the key that 
triggered the event. A positive value represents a Unicode 
character, while a negative value is a key that cannot obviously 
be converted into Unicode. Rather than figure out on a 
device-by-device basis what keys map to what, the Canvas class 
defines a number of constants for commonly-used keys. In
particular, it defines abstract game actions (UP, DOWN, LEFT, 
RIGHT, FIRE, GAME_A, GAME_B, GAME_C, and GAME_D) whose keycode 
mappings can be determined at runtime. During its initialization,
the device can call Canvas.getGameAction to choose which keys map 
best to which actions. 

You might define a base class like this:

public abstract class GameCanvas extends Canvas {
    protected MIDlet midlet;
    protected int    fireKey;
    protected int    leftKey;
    protected int    rightKey;
    protected int    upKey;
    protected int    downKey;
   
    public GameCanvas( MIDlet midlet ){
        this.midlet = midlet;
        fireKey = getKeyCode( FIRE );
        leftKey = getKeyCode( LEFT );
        rightKey = getKeyCode( RIGHT );
        upKey = getKeyCode( UP );
        downKey = getKeyCode( DOWN );
    }
}

And then extend it like this:

public class MyCanvas extends GameCanvas {
    private String message = "Press any key";
   
    public MyCanvas( MIDlet midlet ){
        super( midlet );
    }
   
    protected void paint( Graphics g ){
        g.setColor( 255, 255, 255 );
        g.fillRect( 0, 0, getWidth(), getHeight() );
        g.setColor( 0, 0, 0 );
        g.drawString( message, getWidth()/2, 0,
                      g.TOP | g.HCENTER );
    }
   
    protected void keyPressed( int keyCode ){
        if( keyCode == fireKey ){
            message = "FIRE";
        } else if( keyCode == leftKey ){
            message = "LEFT";
        } else if( keyCode == rightKey ){
            message = "RIGHT";
        } else if( keyCode == upKey ){
            message = "UP";
        } else if( keyCode == downKey ){
            message = "DOWN";
        } else {
            message = getKeyName( keyCode );
        }

        repaint();
    }
}

Pointer events are optional because not all MIDP-enabled devices 
have a pointer. You should take advantage of a pointer whenever 
it makes sense. But you shouldn't assume that one is available.  
You can check if pointer events will be triggered, by calling 
Canvas.hasPointerEvents. The pointer event methods get passed the 
horizontal and vertical position of the pointer:

protected void pointerPressed( int x, int y ){
    // do something here
}

The other way to trap user input is to attach commands to a 
canvas. A command is an abstract representation of an action. 
It has a user-defined label, a type, and a priority. The device 
uses the type to map the command to an appropriate key or button.  
For example, if the device has a standard OK button, specifying
a command type of OK ensures that the OK button will trigger the 
command. The available types are BACK, CANCEL, EXIT, HELP, ITEM, 
OK, SCREEN, and STOP. Some or all of these may map onto the same 
key or button. So when there are conflicts, the priority can be 
used by the device to order the commands appropriately. The 
priority is a positive integer, with 1 being the highest 
priority.

Commands are created using the Command class, as in the 
following:

Command exitCommand = new Command( "Exit", Command.SCREEN, 1 );

You attach the command to a canvas with the addCommand method:

canvas.addCommand( exitCommand );

You must also register a command listener using setListener:

canvas.setListener( listener );

The listener must implement the CommandListener interface. It's 
common for the main MIDlet class to implement CommandListener to 
trap exit commands, as in the following:

// Simple MIDlet

import javax.microedition.midlet.*;

public class MyMIDlet extends MIDlet implements 
                                      CommandListener {

    private Display  display;
    private MyCanvas canvas;
    private Command  exitCommand = new Command( 
                           "Exit", Command.SCREEN, 1 );
   
    public MyMIDlet(){
        display = Display.getDisplay( this );
        canvas = new MyCanvas( this );
        canvas.addCommand( exitCommand );
        canvas.setListener( this );
    }
   
    protected void startApp(){
        display.setCurrent( canvas );
    }
   
    protected void pauseApp(){
    }
  
    protected void destroyApp( boolean unconditional ){
    }
   
    public void exit(){
        destroyApp( true );
        notifyDestroyed();
    }
   
   public void commandAction( Command c, Displayable d ){
        if( c == exitCommand ){
            exit();
        }
    }
}

The CommandListener interface defines a single method, 
commandAction, that is called whenever a command is triggered. 
A reference to the triggered Command object is passed in as 
well as a reference to the display object that was active when 
it was triggered. (The same command can be shared across 
different canvases and also by the high-level API). Of course, 
the listener is responsible for actually carrying out the action.
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

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


- 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 
March 19, 2001

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






