Databases and MIDP, Part 3: Putting Data Mapping to Work

   

by Eric Giguere
May 2004

Part 2 of this series showed you the basics of data mapping. You learned how to store values of primitive types in byte arrays, how to use streams to store and retrieve objects in a record store, and how to extract the stored values from the byte arrays. In this article you'll learn how to distance your applications from these lower-level operations, by extending core classes to include reading and writing operations, and by creating field lists and using them to store and retrieve objects.

 

Extending the Core Classes



One of your goals as a J2ME developer is to minimize your application's use of memory. Ideally, only one copy of a piece of data should be in memory at any given time. When you write data to a ByteArrayOutputStream, however, you never have access to its underlying byte array -- calling toByteArray() returns a copy of the array. This is an unnecessary waste of memory if you're going to save the byte array to a record store immediately. Direct access to the array is just a matter of writing a simple extension to ByteArrayOutputStream called DirectByteArrayOutputStream:

package j2me.io;

import java.io.*;

// A version of ByteArrayOutputStream that gives you
// direct access to the underlying byte array if
// you need it.

public class DirectByteArrayOutputStream 
                       extends ByteArrayOutputStream {

    // Constructs a byte array output stream of default size

    public DirectByteArrayOutputStream(){
        super();
    }

    // Constructs a byte array output stream of given size

    public DirectByteArrayOutputStream( int size ){
        super( size );
    }

    // Returns a reference to the underlying byte array.
    // The actual amount of data in the byte array is
    // obtained via the size method.

    public synchronized byte[] getByteArray(){
        return buf;
    }

    // Swaps in a new byte array for the old one, resetting
    // the count as well.

    public synchronized byte[] swap( byte[] newBuf ){
        byte[] oldBuf = buf;
        buf = newBuf;
        reset();
        return oldBuf;
    }
}

Remember to call the size() method to find out how much data is actually stored in the byte array:

...
DirectByteArrayOutputStream bout = ... 
RecordStore rs = ...

int numBytes = bout.size();
byte[] data = bout.getByteArray();

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

For consistency, you can extend the ByteArrayInputStream class similarly, although it's not as useful as the extension to ByteArrayOutputStream:

package j2me.io;

import java.io.*;

// A version of ByteArrayInputStream that lets you
// replace the underlying byte array.

public class DirectByteArrayInputStream 
                  extends ByteArrayInputStream {

    // Constructs an output stream from the given array

    public DirectByteArrayInputStream( byte buf[] ){
        super( buf );
    }

    // Constructs an output stream from the given subarray

    public DirectByteArrayInputStream( byte buf[],
                                       int offset, 
                                       int length ){
        super( buf, offset, length );
    }

    // Resets the array the stream reads from

    public synchronized void setByteArray( byte[] buf ){
        this.buf = buf;
        this.pos = 0;
        this.count = buf.length;
        this.mark = 0;
    }

    // Resets the array the stream reads from

    public synchronized void setByteArray( byte[] buf, 
                                           int offset,
                                           int length ){
        this.buf = buf;
        this.pos = offset;
        this.count = Math.min( offset + length, buf.length );
        this.mark = offset;
    }
}

Note that ByteArrayInputStream and ByteArrayOutputStream and the two extensions we just wrote use synchronized methods for thread safety. In most cases, though, these streams are used only from a single thread, making synchronization unnecessary. If your application will read and write a lot of data, consider creating your own unsynchronized versions of these classes to gain a bit of extra speed.

You can easily extend DataInputStream and DataOutputStream as well. If you write integer arrays in several places, for example, use this extension of DataOutputStream:

package j2me.io;

import java.io.*;

public class ExtendedDataOutputStream extends DataOutputStream {
    public ExtendedDataOutputStream( OutputStream out ){
        super( out );
    }

    public final void writeIntArray( int[] arr ) 
                                     throws IOException {
        int size = arr.length;
        writeInt( size );
        for( int i = 0; i < size; ++i ){
            writeInt( arr[i] );
        }
    }
}

You could instead put this kind of code in a helper class, because you don't need access to any protected members.

 

Creating Record Fields



You now have all the tools you need to create a field-based record store, one in which each record is a set of named fields of types you specify. You use two classes to manage the record store. The first, FieldList, manages the information about the fields themselves -- the metadata:

package j2me.rms;

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

// Maintains information about the fields in a
// field-based record store. Currently just a list of
// field types and (optional) field names, but could
// easily be expanded to store other information.

public class FieldList {

    private static final int VERSION = 1;

    // The basic field types.

    public static final byte TYPE_BOOLEAN = 1;
    public static final byte TYPE_BYTE = 2;
    public static final byte TYPE_CHAR = 3;
    public static final byte TYPE_SHORT = 4;
    public static final byte TYPE_INT = 5;
    public static final byte TYPE_LONG = 6;
    public static final byte TYPE_STRING = 7;

    // Constructs an empty list.

    public FieldList(){
    }

    // Constructs a list of the given size.

    public FieldList( int numFields ){
        if( numFields < 0 || numFields > 255 ){
            throw new IllegalArgumentException( 
                       "Bad number of fields" );
        }

        _types = new byte[ numFields ];
        _names = new String[ numFields ];
    }

    // Returns the number of fields.

    public int getFieldCount(){
        return _types != null ? _types.length : 0;
    }

    // Returns the name of a field.

    public String getFieldName( int index ){
        String name = _names[ index ];
        return name != null ? name : "";
    }

    // Returns the type of a field.

    public byte getFieldType( int index ){
        return _types[ index ];
    }

    // Reads the field list from a byte array.

    public void fromByteArray( byte[] data )
                               throws IOException {
        ByteArrayInputStream bin = 
                  new ByteArrayInputStream( data );
        fromDataStream( new DataInputStream( bin ) );
        bin.close();
    }

    // Reads the fields list from a data stream.

    public void fromDataStream( DataInputStream din )
                                throws IOException {
        int version = din.readUnsignedByte();
        if( version != VERSION ){
            throw new IOException( "Incorrect version " +
                  version + " for FieldList, expected " +
                  VERSION );
        }

        int numFields = din.readUnsignedByte();

        _types = new byte[ numFields ];
        _names = new String[ numFields ];

        if( numFields > 0 ){
            din.readFully( _types );
    
            for( int i = 0; i < numFields; ++i ){
                _names[i] = din.readUTF();
            }
        }
    }

    // Reads a field list from a record store.

    public void fromRecordStore( RecordStore rs, int index )
                                 throws IOException, 
                                        RecordStoreException {
        fromByteArray( rs.getRecord( index ) );
    }

    // Sets the name of a field.

    public void setFieldName( int index, String name ){
        _names[ index ] = name;
    }

    // Sets the type of a field.

    public void setFieldType( int index, byte type ){
        _types[ index ] = type;
    }

    // Stores the fields list to a byte array

    public byte[] toByteArray() throws IOException {
        ByteArrayOutputStream bout = 
                     new ByteArrayOutputStream();
        toDataStream( new DataOutputStream( bout ) );
        byte[] data = bout.toByteArray();
        bout.close();
        return data;
    }

    // Stores the fields list to a data stream

    public void toDataStream( DataOutputStream out )
                              throws IOException {
        out.writeByte( VERSION );

        int count = getFieldCount();

        out.writeByte( count );

        if( count > 0 ){
            out.write( _types, 0, count );

            for( int i = 0; i < count; ++i ){
                out.writeUTF( getFieldName( i ) );
            }
        }
    }

    // Writes a field list to a record store.

    public int toRecordStore( RecordStore rs, int index )
                               throws IOException, 
                                      RecordStoreException {
        byte[]  data = toByteArray();
        boolean add = true;

        if( index > 0 ){
            try {
                rs.setRecord( index, data, 0, data.length );
                add = false;
            }
            catch( InvalidRecordIDException e ){
            }
        }

        // If the record doesn't actually exist yet,
        // go ahead and create it by inserting dummy
        // records ahead of it

        if( add ){
            synchronized( rs ){
                int nextID = rs.getNextRecordID();
                if( index <= 0 ) index = nextID;
    
                while( nextID < index ){
                    rs.addRecord( null, 0, 0 );
                }
    
                if( nextID == index ){
                    rs.addRecord( data, 0, data.length );
                }
            }
        }

        return index;
    }

    private String[] _names;
    private byte[]   _types;
}

At its core, a FieldList instance is just a wrapper for two arrays. The first array stores the types of each field, the second stores the name of each field. The names are optional; it's the types that are important, because they determine how data will be read from and written to a record. All standard Java primitive data types are supported, plus the String type. Here's how you define the fields for storing a list of departments in an organization:

...
FieldList depts = new FieldList( 3 );
depts.setFieldType( 0, FieldList.TYPE_SHORT );
depts.setFieldName( 0, "ID" );
depts.setFieldType( 1, FieldList.TYPE_STRING );
depts.setFieldName( 1, "Name" );
depts.setFieldType( 2, FieldList.TYPE_INT );
depts.setFieldName( 2, "ManagerID" );
...

FieldList

...
FieldList list = ... // a field list
RecordStore rs = RecordStore.openRecordStore( "foo", true );
if( rs.getNumRecords() == 0 ){ // empty, store it
    list.toRecordStore( rs, -1 );
}
...

The second parameter of the toRecordStore() method identifies the record to use for saving the field data. A negative value indicates that a new record should be appended.

The second class you need to manage a field-based record store is FieldBasedStore, a wrapper around the store that will manage the actual reading and writing:

package j2me.rms;

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

// A wrapper class for a record store that allows the
// records to be accessed as a set of fields. The field
// definitions are maintained separately using a FieldList
// object, which can be stored as part of the record store
// or separately.

public class FieldBasedStore {

    // Some useful constants

    public static Boolean TRUE = new Boolean( true );
    public static Boolean FALSE = new Boolean( false );

    // Markers for the types of string we support

    private static final byte NULL_STRING_MARKER = 0;
    private static final byte UTF_STRING_MARKER = 1;

    // Constructs a field store where the field list is
    // assumed to be stored in the first record.

    public FieldBasedStore( RecordStore rs )
                            throws IOException,
                                   RecordStoreException {
        this( rs, 1 );
    }

    // Constructs a field store where the field list is
    // stored in the given record.

    public FieldBasedStore( RecordStore rs, int fieldListID )
                            throws IOException,
                                   RecordStoreException {
        this( rs, loadFieldList( rs, fieldListID ) );
    }

    // Constructs a field store with the given field list.

    public FieldBasedStore( RecordStore rs, FieldList list ){
        _rs = rs;
        _fieldList = list;
    }

    // Adds a new record to the store. Returns the new
    // record ID.

    public synchronized int addRecord( Object[] fields )
                                 throws IOException,
                                        RecordStoreException {
        writeStream( fields );
        byte[] data = _bout.getByteArray();
        return _rs.addRecord( data, 0, data.length );
    }

    // Returns the current field list.

    public FieldList getFieldList(){
        return _fieldList;
    }

    // Returns the record store.

    public RecordStore getRecordStore(){
        return _rs;
    }

    // Loads the field list from the record store.

    private static FieldList loadFieldList( RecordStore rs,
                                            int fieldListID )
                                 throws IOException,
                                        RecordStoreException {
        FieldList list = new FieldList();
        list.fromRecordStore( rs, fieldListID );
        return list;
    }

    // Prepares the store for input by making sure that
    // the data buffer is big enough. The streams are
    // reused.

    private void prepareForInput( int size ){
        if( _buffer == null || _buffer.length < size ){
            _buffer = new byte[ size ];
        }

        if( _bin == null ){
            _bin = new DirectByteArrayInputStream( _buffer );
            _din = new DataInputStream( _bin );
        } else {
            _bin.setByteArray( _buffer );
        }
    }

    // Prepares the store for output. The streams are reused.

    private void prepareForOutput(){
        if( _bout == null ){
            _bout = new DirectByteArrayOutputStream();
            _dout = new DataOutputStream( _bout );
        } else {
            _bout.reset();
        }
    }

    // Reads a field from the buffer.

    private Object readField( int type ) throws IOException {
        switch( type ){
            case FieldList.TYPE_BOOLEAN:
                return _din.readBoolean() ? TRUE : FALSE;
            case FieldList.TYPE_BYTE:
                return new Byte( _din.readByte() );
            case FieldList.TYPE_CHAR:
                return new Character( _din.readChar() );
            case FieldList.TYPE_SHORT:
                return new Short( _din.readShort() );
            case FieldList.TYPE_INT:
                return new Integer( _din.readInt() );
            case FieldList.TYPE_LONG:
                return new Long( _din.readLong() );
            case FieldList.TYPE_STRING: {
                byte marker = _din.readByte();
                if( marker == UTF_STRING_MARKER ){
                    return _din.readUTF();
                }
            }
        }

        return null;
    }

    // Reads the record at the given ID and returns it as
    // a set of objects that match the types in the 
    // field list.

    public synchronized Object[] readRecord( int recordID )
                                 throws IOException,
                                        RecordStoreException {
        prepareForInput( _rs.getRecordSize( recordID ) );
        _rs.getRecord( recordID, _buffer, 0 );

        int count = _fieldList.getFieldCount();
        Object[] fields = new Object[ count ];

        for( int i = 0; i < count; ++i ){
            fields[i] = readField(_fieldList.getFieldType(i));
        }

        return fields;
    }

    // Converts an object to a boolean value.

    public static boolean toBoolean( Object value ){
        if( value instanceof Boolean ){
            return ((Boolean) value).booleanValue();
        } else if( value != null ){
            String str = value.toString().trim();

            if( str.equals( "true" ) ) return true;
            if( str.equals( "false" ) ) return false;

            return( toInt( value ) != 0 );
        }

        return false;
    }

    // Converts an object to a char.

    public static char toChar( Object value ){
        if( value instanceof Character ){
            return ((Character) value).charValue();
        } else if( value != null ){
            String s = value.toString();
            if( s.length() > 0 ){
                return s.charAt( 0 );
            }
        }

        return 0;
    }

    // Converts an object to an int. This code
    // would be much simpler if the CLDC supported
    // the java.lang.Number class.

    public static int toInt( Object value ){
        if( value instanceof Integer ){
            return ((Integer) value).intValue();
        } else if( value instanceof Boolean ){
            return ((Boolean) value).booleanValue() ? 1 : 0;
        } else if( value instanceof Byte ){
            return ((Byte) value).byteValue();
        } else if( value instanceof Character ){
            return ((Character) value).charValue();
        } else if( value instanceof Short ){
            return ((Short) value).shortValue();
        } else if( value instanceof Long ){
            return (int) ((Long) value).longValue();
        } else if( value != null ){
            try {
                return Integer.parseInt( value.toString() );
            }
            catch( NumberFormatException e ){
            }
        }

        return 0;
    }

    // Converts an object to a long. This code
    // would be much simpler if the CLDC supported
    // the java.lang.Number class.

    public static long toLong( Object value ){
        if( value instanceof Integer ){
            return ((Integer) value).longValue();
        } else if( value instanceof Boolean ){
            return ((Boolean) value).booleanValue() ? 1 : 0;
        } else if( value instanceof Byte ){
            return ((Byte) value).byteValue();
        } else if( value instanceof Character ){
            return ((Character) value).charValue();
        } else if( value instanceof Short ){
            return ((Short) value).shortValue();
        } else if( value instanceof Long ){
            return ((Long) value).longValue();
        } else if( value != null ){
            try {
                return Long.parseLong( value.toString() );
            }
            catch( NumberFormatException e ){
            }
        }

        return 0;
    }

    // Writes a field to the output buffer.

    private void writeField( int type, Object value )
                                 throws IOException {
        switch( type ){
            case FieldList.TYPE_BOOLEAN:
                _dout.writeBoolean( toBoolean( value ) );
                break;
            case FieldList.TYPE_BYTE:
                _dout.write( (byte) toInt( value ) );
                break;
            case FieldList.TYPE_CHAR:
                _dout.writeChar( toChar( value ) );
                break;
            case FieldList.TYPE_SHORT:
                _dout.writeShort( (short) toInt( value ) );
                break;
            case FieldList.TYPE_INT:
                _dout.writeInt( toInt( value ) );
                break;
            case FieldList.TYPE_LONG:
                _dout.writeLong( toLong( value ) );
                break;
            case FieldList.TYPE_STRING:
                if( value != null ){
                    String str = value.toString();
                    _dout.writeByte( UTF_STRING_MARKER );
                    _dout.writeUTF( str );
                } else {
                    _dout.writeByte( NULL_STRING_MARKER );
                }
                break;
        }
    }

    // Writes a set of fields to the given record. The
    // fields must be compatible with the types in
    // the field list.

    public synchronized void writeRecord( int recordID,
                                          Object[] fields )
                                 throws IOException,
                                        RecordStoreException {
        writeStream( fields );
        byte[] data = _bout.getByteArray();
        _rs.setRecord( recordID, data, 0, data.length );
    }

    // Writes a set of fields to the output stream.

    private void writeStream( Object[] fields )
                                       throws IOException {
        int count = _fieldList.getFieldCount();
        int len = ( fields != null ? fields.length : 0 );

        prepareForOutput();

        for( int i = 0; i < count; ++i ){
            writeField( _fieldList.getFieldType( i ),
                        ( i < len ? fields[i] : null ) );
        }
    }

    private DirectByteArrayInputStream  _bin;
    private DirectByteArrayOutputStream _bout;
    private byte[]                      _buffer;
    private DataInputStream             _din;
    private DataOutputStream            _dout;
    private FieldList                   _fieldList;
    private RecordStore                 _rs;
}

To create a FieldBasedStore you need an open RecordStore instance and a FieldList instance. You can read the latter from the record store itself, implicitly, in the process of constructing the FieldBasedStore:

...
RecordStore rs = ... // an open record store
FieldBasedStore fstore = new FieldBasedStore( rs );
...

Or you can specify it explicitly:

...
RecordStore rs = ... // an open record store
FieldList list = ... // a field list
FieldBasedStore fstore = new FieldBasedStore( rs, list );
...

A FieldBasedStore treats each record as an object array. The types in the array match the types described in the field list. Consider the list of departments shown earlier, each consisting of a department identifier, a department name, and a manager identifier. You can add a record thus:

...
Object[] fields = new Object[]{
    new Short( 1 ), "Accounting", new Integer( 100 )
};

int recordID = fstore.addRecord( fields );
...

Note that the record-writing code in FieldBasedStore is intelligent enough to perform the "obvious" data conversions, so you could also do it this way:

...
Object[] fields = new Object[]{ "1", "Accounting", "100" };
int recordID = fstore.addRecord( fields );
...

Reading the record is just as trivial:

...
Object[] fields = fstore.readRecord( recordID );
for( int i = 0; i < fields.length; ++i ){
    System.out.println( "Field: " +
       fstore.getFieldList().getFieldName( i ) +
       " Value: " + fields[i] );
}
...

You can modify a record at any time by rewriting the array:

...
Object[] fields = fstore.readRecord( recordID );
fields[2] = "134"; // change the manager
fstore.writeRecord( recordID, fields );
...

Here's an example of a MIDlet that uses a couple of field-based stores to store and retrieve data about employees and departments. It also dumps out the contents of the record stores using the RMSAnalyzer class discussed in Part 1, just to show you how things are being stored.

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

// A simple MIDlet for testing RMS mappings
// done using the FieldBasedStore class.

public class RMSMappings extends MIDlet
                 implements CommandListener {

    private Display    display;

    public static final Command exitCommand =
                         new Command( "Exit",
                                      Command.EXIT, 1 ); 
    public static final Command testCommand =
                         new Command( "Test",
                                      Command.SCREEN, 1 );

    private static Object[][] empList = {
        new Object[]{ "1", "Mary", "CEO", "100", "F" },
        new Object[]{ "2", "John", "CFO", "200", "M" },
        new Object[]{ "3", "Pat", "Closem", "300", "F" },
        new Object[]{ "4", "PJ", "Admin", "100", "M" },
    };

    private static Object[][] deptList = {
        new Object[]{ "100", "Executive", "1" },
        new Object[]{ "200", "Operations", "2" },
        new Object[]{ "300", "Sales", "1" },
    };

    public RMSMappings(){
    }

    public void commandAction( Command c,
                               Displayable d ){
        if( c == exitCommand ){
            exitMIDlet();
        } else if( c == testCommand ){
            runTest();
        }
    }

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

    public void exitMIDlet(){
        notifyDestroyed();
    }

    public Display getDisplay(){ return display; }

    protected void initMIDlet(){
        display.setCurrent( new MainForm() );
    }

    protected void pauseApp(){
    }

    private void printRecord( FieldBasedStore store,
                              int recordID ){
        try {
            FieldList list = store.getFieldList();
            Object[]  fields = store.readRecord( recordID );

            if( fields.length != list.getFieldCount() ){
                System.out.println( "Error: bad count" );
                return;
            }

            System.out.println( "Record " + recordID + ":" );

            for( int i = 0; i < fields.length; ++i ){
                System.out.println( "  " +
                      list.getFieldName( i ) + ": " +
                      fields[i] );
            }
        }
        catch( RecordStoreException e  ){
        }
        catch( IOException e ){
        }
    }

    private void runTest(){
        // First delete the record stores...

        System.out.println( "Deleting record stores..." );

        String[] names = RecordStore.listRecordStores();

        for( int i = 0; i < names.length; ++i ){
            try {
                RecordStore.deleteRecordStore( names[i] );
            } catch( RecordStoreException e ){
                System.out.println( "Could not delete " +
                                    names[i] );
            }
        }

        // Create two record stores, one with a field list
        // stored in the first record and the second with
        // a field list stored separately (in the app)

        RecordStore     empRS = null;
        RecordStore     deptRS = null;
        FieldList       empFields = new FieldList( 5 );
        FieldList       deptFields = new FieldList( 3 );
        FieldBasedStore employees;
        FieldBasedStore departments;

        empFields.setFieldType( 0, FieldList.TYPE_INT );
        empFields.setFieldName( 0, "ID" );
        empFields.setFieldType( 1, FieldList.TYPE_STRING );
        empFields.setFieldName( 1, "Given Name" );
        empFields.setFieldType( 2, FieldList.TYPE_STRING );
        empFields.setFieldName( 2, "Last Name" );
        empFields.setFieldType( 3, FieldList.TYPE_BOOLEAN );
        empFields.setFieldName( 3, "Active" );
        empFields.setFieldType( 4, FieldList.TYPE_CHAR );
        empFields.setFieldName( 4, "Sex" );

        System.out.println( "Initializing employees" );

        try {
            empRS = RecordStore.openRecordStore( "empRS",
                                                 true );
            // now store the field list in the RS
            empFields.toRecordStore( empRS, -1 );
            employees = new FieldBasedStore( empRS );
        }
        catch( RecordStoreException e ){
            System.out.println( "Could not create empRS" );
            return;
        }
        catch( IOException e ){
            System.out.println( "Error storing field list" );
            return;
        }

        System.out.println( "Initializing departments" );

        deptFields.setFieldType( 0, FieldList.TYPE_INT );
        deptFields.setFieldName( 0, "ID" );
        deptFields.setFieldType( 1, FieldList.TYPE_STRING );
        deptFields.setFieldName( 1, "Name" );
        deptFields.setFieldType( 2, FieldList.TYPE_INT );
        deptFields.setFieldName( 2, "Manager" );

        try {
            deptRS = RecordStore.openRecordStore( "deptRS",
                                                  true );
            departments = new FieldBasedStore( deptRS,
                                               deptFields );
        }
        catch( RecordStoreException e ){
            System.out.println( "Could not create deptRS" );
            return;
        }

        int[] empRecordID;
        int[] deptRecordID;
        int   i;

        // Add the data...

        try {
            empRecordID = new int[ empList.length ];

            for( i = 0; i < empList.length; ++i ){
                empRecordID[i] =
                       employees.addRecord( empList[i] );
            }

            deptRecordID = new int[ deptList.length ];

            for( i = 0; i < deptList.length; ++i ){
                deptRecordID[i] =
                    departments.addRecord( deptList[i] );
            }
        }
        catch( RecordStoreException e ){
            System.out.println( "Error adding record" );
            return;
        }
        catch( IOException e ){
            System.out.println( "Error writing field" );
            return;
        }

        // Now fetch the data back and print it...

        System.out.println( "---- Employee data ----" );

        for( i = 0; i < empRecordID.length; ++i ){
            printRecord( employees, empRecordID[i] );
        }

        System.out.println( "---- Department data ----" );
    
        for( i = 0; i < deptRecordID.length; ++i ){
            printRecord( departments, deptRecordID[i] );
        }
    
        System.out.println( "Closing empRS" );

        try {
            empRS.closeRecordStore();
        }
        catch( RecordStoreException e ){
            System.out.println( "Error closing empRS" );
        }

        System.out.println( "Closing deptRS" );

        try {
            deptRS.closeRecordStore();
        }
        catch( RecordStoreException e ){
            System.out.println( "Error closing deptRS" );
        }

        System.out.println( "Dumping record stores..." );

        // Analyze them...

        RMSAnalyzer analyzer = new RMSAnalyzer(
                      new RMSAnalyzer.SystemLogger( 10 ) );
        analyzer.analyzeAll();
    }

    protected void startApp()
                      throws MIDletStateChangeException {
        if( display == null ){ 
            display = Display.getDisplay( this );
            initMIDlet();
        }
    }

    public class MainForm extends Form {
        public MainForm(){
            super( "RMSMappings" );

            addCommand( exitCommand );
            addCommand( testCommand );

            setCommandListener( RMSMappings.this );
        }
    }
}

As they stand now, the FieldBasedStore and FieldList classes could use some improvement. The field-based store could be cursor-based, for example, like a JDBC result set, and let you move from record to record and fetch individual field values. The store could cache records. The field list could return the index of a field when given its name. These and other improvements all come at a cost, of course, so you have to balance what you need with what makes sense on your platform. A generic field-based approach works best when you don't know the structure of the data beforehand -- which is not usually the case. Usually, the best solution is to wrap known object types in persistent objects. I present the generic approach here primarily to show you how far you can go.

 

What's Next



In Part 4 we'll look at more advanced aspects of using RMS: traversing record stores and filtering records.

Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.
false ,,,,,,,,,,,,,,,