Description of using Perst Lite in ProScout

The following code, with comments and explanatory text, explains how key features of the Perst Lite object-oriented embedded database are used within the ProScout midlet.

In Perst Lite, all objects in the database storage should be accessible through a single root object. So, this object should contain collections which keep references to all the application’s top-level objects.

In the ProScout example, this root object contains an index of players by last name and two indices of teams: by team ID and name. Also, each persistence-capable object should be derived from the Persistent base class:

public class Root extends Persistent {
    Index teamId;
    Index teamName;
    Index playerLName;
    ...

    // The default constructor (the constructor without parameters, shown
    // below), should not be used for object initialization since it is used
    // to instantiate every object each time it is loaded from the database.
    // So this class should either not have a constructor at all (in which
    // case it will be generated automatically by the compiler), or it should
    // provide an empty default constructor and a constructor used for object
    // initialization (usually the class is passed a reference to the storage
    // since the storage is needed to create Perst collections).
    
    public Root() {}

    // The constructor below is called once during
    // database initialization. It initializes the root object and
    // creates indices to access teams
    // and players.

    public Root(Storage db) { 
        super(db);
        // Create unique index for team identifiers
        teamId = db.createIndex(Types.Int, true);
        // Create unique index for team names
        teamName = db.createIndex(Types.String, true);
        // Create non-unique index for last name of the players
        playerLName = db.createIndex(Types.String, false);
    }
}
Since MIDP 2.0 doesn't provide reflection, we need to explicitly specify the method for packing/unpacking an object. These methods can be written manually or generated using Perst Lite’s SerGen utility. In the following example, the methods are written manually:
// All persistence-capable classes should be derived from the
// org.garret.perst.Persistent base class.

public class Player extends Persistent {
    Team   team;
    String fname;
    String lname;
    int    jersey;
    String address;
    String city;
    String state;
    String zip;
    String phone;
    String school;
    int    gradyear;
    int    height;

    // Serialize the object
    public void writeObject(IOutputStream out) { 
        out.writeObject(team);
        out.writeString(fname);
        out.writeString(lname);
        out.writeInt(jersey);
        out.writeString(address);
        out.writeString(city);
        out.writeString(state);
        out.writeString(zip);
        out.writeString(phone);
        out.writeString(school);
        out.writeInt(gradyear);
        out.writeInt(height);
    }

    // Deserialize the object
    public void readObject(IInputStream in) { 
        team = (Team)in.readObject();
        fname = in.readString();
        lname = in.readString();
        jersey = in.readInt();
        address = in.readString();
        city = in.readString();
        state = in.readString();
        zip = in.readString();
        phone = in.readString();
        school = in.readString();
        gradyear = in.readInt();
        height = in.readInt();
    }
    ...
};
First, we need to open a database. This is done using the midlet’s startApp method:
public class UniAR extends MIDlet implements CommandListener 
{
    ...
    public void startApp()
    {
        // Get instance of Perst storage
        db = StorageFactory.getInstance().createStorage();
        // Open the database with a given database name and specified page
	// pool (database cache) size
        db.open("uniar.dbs", PAGE_POOL_SIZE);
        // There is one root object in the database. 
        Root root = (Root)db.getRoot();
        if (root == null) {
        // if root object was not specified, then storage is not yet
        // initialized
            // Perform initialization:
            // ... create root object
            root = new Root(db);
            // ... register new root object
            db.setRoot(root); 
            // ... and import data from resource files
            importData();
        }
        ...
    }
}
During import we create persistent objects, include them in proper indices and periodically commit transactions (since committing a transaction is an expensive operation, committing after each insertion will cause significant performance degradation):
public class UniAR extends MIDlet implements CommandListener 
{
    ...
    void importData()
    {
        ...
        while ((row = readCSVLine(17, in)) != null) {
            // Create new persistence-capable object
            Team team = new Team(db, row);
            // Put created team object in team ID index
            root.teamId.put(new Key(team.id), team);
            // Put created team object in team name index
            root.teamName.put(team.name.toLowerCase(), team);
            gauge.setValue(++nRows);

            if (nRows % TRANSACTION_PERIOD == 0) {       
                // Since committing a transaction is expensive operation,
                // committing transactions after each insertion will cause
 	        // significant performance degradation. But we have to periodically
 	        // perform commits to avoid memory overflow, since all modified and
	        // created persistent objects are held in memory.

                db.commit();
                // We need to re-fetch the root object since committing a
                // transaction caused a cleanup of the object cache to
                // be performed, and references to persistent objects may
                // no longer be valid
                root = (Root)db.getRoot();
            }
            if (nRows == maxRowsToLoad) { 
                break;
            }
        } 
        ...
    }
}
Perst Lite uses a persistence by reachability approach, meaning that a persistence-capable object automatically becomes persistent and is stored in the database during a transaction commit if that object is referenced from some other persistent object (including Perst collections).

Once the database is loaded, it is possible to search objects using one of the indices. Below is an example of searching a team by prefix:

class TeamSearchForm extends Form implements CommandListener 
{ 
    ...
    public void commandAction(Command c, Displayable d)
    {
        if (c == UniAR.SEARCH_CMD) { 
            // Get storage root object
            Root root = (Root)uar.db.getRoot();
            // Perform prefix search
            Iterator iterator = root.teamName.prefixIterator(pattern.getString().toLowerCase());
            if (iterator.hasNext()) {
            // pass iterator through all located objects to the TeamForm
		// constructor
                new TeamForm(uar, iterator, uar.menu);
            } else { 
                uar.message("No results");
            }
        } else { 
            Display.getDisplay(uar).setCurrent(uar.menu);
        }
    }
}
And TeamForm uses the iterator next() method to traverse the list of the selected teams:
class TeamForm extends Form implements CommandListener 
{ 
    ...
    TeamForm(UniAR uar, Iterator iterator, Displayable parent) {
        ...
        this.iterator = iterator;
        team = (Team)iterator.next();
        if (iterator != null && iterator.hasNext()) {
            addCommand(UniAR.NEXT_CMD);
        }
    }

    public void commandAction(Command c, Displayable d)
    {
        if (c == UniAR.NEXT_CMD) { 
            new TeamForm(uar, iterator, parent);
        }
        ...
    }
}


Finally, when the application is terminated it should close the database:
public class UniAR extends MIDlet implements CommandListener 
    ...
    protected void destroyApp(boolean unconditional) {
        // When the application is terminated, do not forget to close the
	// database
        db.close();
    }  
}