Developer Product Briefs

An Object Database for Quartz

The UIQ platform supports PersonalJava and MIDP. Discover how to reverse-engineer and modify an address book application to take advantage of a powerful object database library.

An Object Database for Quartz
Reverse-engineer and modify an address book application to take advantage of a powerful object database library for mobile devices.
by Rick Grehan

April 17, 2006

Symbian's UIQ platform, also known as Quartz, is an environment tailored for handheld phones equipped with pen input and a quarter-sized VGA (though sometimes smaller) display. Although the primary development language for UIQ devices is C++, the Quartz operating system supports two Java environments: PersonalJava and Mobile Information Device Profile (MIDP).

In keeping with a fine tradition that began with its Epoc operating system, Symbian provides a remarkably complete emulator that you can download from its Web site. The emulator is bundled with the UIQ SDK, and it includes documentation; supporting libraries (class files and their source); and a variety of tools useful for debugging, compressing files, and packaging applications for ultimate deployment on a real Quartz system (see Resources).

What's most useful in this bundle, though, are the Java Quartz application examples. Their accompanying source makes them ideal for the sort of learning that one only gets by reverse-engineering code that already works. Here we'll look at a particular application, the AddressBook example, and we'll not only reverse-engineer it, we'll make some modifications that demonstrate how our applications can enjoy an unexpectedly powerful object database library because UIQ supports PersonalJava. In this instance, that library is db4objects (db4o), an open source object database (see Resources).

Let's first explore the database that currently serves as the persistent storage back end for AddressBook. Once we're familiar with it, we can reroute the application's database plumbing.

A Modified AddressBook
The AddressBook application example supplied with the Quartz emulator and SDK demonstrates at least two aspects of Quartz programming. First, it illustrates basic interactions with the Quartz user interface. In particular, it shows off the QFrame class—an elaboration of the awt's Frame class—that uses the paradigm of stacked cards as the abstraction for managing multiple views. Second, it is an easy-to-follow example of using the ContactDatabase that is supplied as an implementation of the javax.pim.database interface.

As its name suggests, the ContactDatabase class describes a database that stores contact information: name, address, phone number, and so on. Individual contacts are kept in ContactCard objects, for which the ContactDatabase serves as a container. In turn, each ContactCard is composed of ItemField objects that store the data values associated with a contact.

If you're familiar with relational databases, this analogy might help: A ContactDatabase corresponds to a single-table database, a ContactCard corresponds to a single row in the table, and ItemField objects correspond to columns in the table. Of course, as with all analogies, this one goes only so far. ItemField objects can be gathered into an AggregateField object, which provides support for multivalued data fields (and for which there is no real counterpart in the relational model).

An AggregateField object works something like an ArrayList. You can add an ItemField object to an AggregateField (either by appending it to the end or by inserting it at a particular index); remove ItemField members; or update ItemField members in the aggregate. The example given most often for using an AggregateField is storing a contact's name (which is really a composition of parts). A name's AggregateField might consist of LastName, FirstName, MiddleName, and Salutation ItemField objects. Consequently, the name AggregateField can be manipulated as a single item, while retaining the distinctions of the different ItemField components.

In addition, you can also use the getField() method, which you use to fetch an item from the ContactDatabase, to fetch an AggregateField. Then the AggregateField class also defines a getField() method that you can use to peel apart the AggregateField's component ItemField objects.

The ContactDatabase predefines a number of constant (static final) strings that specify particular ItemField and AggregateField members. For example, the ContactDatabase.TEL string identifies an AggregateField that includes ItemField members corresponding to a work phone number, home phone number, fax number, cell number, and more. ContactDatabase also provides string values of various parameters that can be used for identifying ItemField entries within an AggregateField. For example, you can use HOME to identify the home phone number within the TEL AggregateField.

The result is a flexible database structure that is ideal for maintaining contact information. Nevertheless, it's a bit laborious to move data into and out of the database. For example, the AddressBook application defines the ContactDetail class to hold the data drawn from each ContactCard. And the AddressBook must use code like this to move data from a ContactCard object in the database into a ContactDetail object for viewing (see Listing 1).

Notice the use of an AggregateField for the name. Within the name, there are two ItemField components: FAMILY_NAME and GIVEN_NAME. The code shown in Listing 1 continues along the same lines as what is shown, pulling individual AggregateField and ItemField objects and storing them in the theDetails object (which is a member of the ContactDetail class).

While on the one hand the AddressBook application uses a database that is digestible by other UIQ applications, on the other hand it yields code that is somewhat reminiscent of the effort needed to move data between objects in the application space to rows in a relational database. A kind of mirror-image of this code must be executed to move data from the ContactDetail into the ContactCard object (when data is placed into the database).

There's an easier way to manage the AddressBook's persistent storage, one that doesn't involve translation code and that allows us to store, retrieve, and delete objects "as they are."

Surgically Altered
The AddressBook example provides a fine test bed for illustrating at least one of the benefits of an object database system. In this instance, that benefit is the ease with which data is stored to and received from the database. To implement this illustration, we'll be calling on a particularly facile object database engine called db4o (Database for Objects).

The db4o database engine has numerous attractive characteristics. It is open source, which might be its most appealing attribute were it not for db4o's carefully tuned and easy-to-master API. At first glance, one might assume that the paucity of methods in db4o's API repertoire is an indication of limited features. The truth is quite the opposite; db4o has plenty of features, it's just that they are layered in such a way that the most often used operations are quickly available on the surface. More advanced capabilities are available deeper down in the API.

Recall that in the AddressBook application the ContactDetail object holds the data that will be manipulated by the program. Consequently, the application must move data between the ContactCard object and the ContactDetail object. The latter is, in effect, that subset of available ContactDatabase fields that the AddressBook application is interested in.

The authors of the AddressBook application isolated all the database access code in the AddressBookModel class. Therefore, similar to the code shown previously, AddressBookModel must provide the createContact() method (see Listing 2).

ItemField or AggregateField objects have to be constructed and populated with data from the input fields (the theDetails object, in this case). When all the field objects are built, the contact card itself is populated with them, and the card is put into the database with this code:

// add fields to contact
ContactCard contact = 
  new ContactCard();
contact.addField(name);
contact.addField(role);
contact.addField(mobileTel);
contact.addField(workTel);
contact.addField(homeTel);
contact.addField(fax);
contact.addField(url);
contact.addField(org);
contact.addField(email);
addressbook.addItem(contact);

In our db4o-modified version, there is no separate ContactCard object to work with. We can manipulate ContactDetail objects directly. We don't have to create any ItemField or AggregateField objects and then add them to a ContactCard. Our new code for createContact() is this simple:

public boolean  createContact(
  ContactDetail theDetails)
{
  addressbook.set(theDetails);
  addressbook.commit();
  return true; 
}

In this code, addressbook is an object of db4o's ObjectContainer class. ObjectContainer models the database. (For example, you initiate access to a db4o database by calling the ObjectContainer class's open() method.) The set() method takes as its sole argument the object that we want to put into the database. Notice that we need not tell set() anything about the object's structure. One of db4o's particularly nice features is its ability to invisibly spider an object's structure and do all the work necessary to deduce that object's architecture with no intervention on our part. We just tell db4o: "Put this object into the database." It does the rest.

Notice also that a commit() method call follows the set() call. Whenever you issue an operation that modifies the database, db4o automatically initiates a transaction. The commit() guarantees that the transaction is properly terminated. (Were the application to crash immediately after the commit() call, the database would be recoverable. db4o's transactions keep the database safe from unexpected disasters.)

The new code for deleting an entry from the database is similar to the original method. Here's the original code:

public boolean deleteContact()
{
  try
  {
    if(currentContact != null)
    {
      println("Deleting " + 
        currentContact.getName());
      addressbook.deleteItem(
        currentContact);
      currentContact = null;
      return true;
    }
  }catch(DatabaseException dbe)
  {
    println(
      "Problem deleting Contact 
      Card " + 
      currentContact.getName());
    return false;
  } 
  return false;
}

And the new code for deleting the current entry is:

public boolean deleteContact()
{
  if(currentContact!=null)
  {
    println("Deleting " + 
      currentContact.getName());
    addressbook.delete(
      currentContact);
    addressbook.commit();
    currentContact = null;
    return true;
  } 
  return false;
}

The only real differences are the (mostly invisible) facts that addressbook in the original code is a ContactDatabase, while in the new version addressbook is a db4o object container; and the original currentContact is a ContactCard object, while in our new version, it is the ContactDetail object. We also have a bit more confidence in db4o than the original authors had in the ContactDatabase, as our new code requires no exception handling.

Finding That Someone
Searching for a specific contact card illustrates more interesting distinctions between db4o and the original contact database. In this case, we have to do a bit of work in our new code that, in the original, the javax.pim.database.Database interface does automatically (or, at least, that the Database implementation in ContactDatabase does). ContactDatabase implements this method:

Iterator items(
  String name, Object value)

The items() method takes a name argument, which gives the name of the ItemField that the search is interested in. The value object tells items() to return all items in the database whose ItemField entries of name match value. If we want all the items with EMAIL ItemField matches "[email protected]," we would issue the call:

results = addressbook.items(
  "EMAIL","[email protected]");

This snippet of code is virtually identical to the corresponding code in AddressBook. It deduces the name item (to identify which ItemField to search on) by tracking which text input field the user selected for a search. That field also carries the information that will be passed in as the value object. We can, at least, employ that same technique for locating which member of ContactDetail is the search target. Finding the matching objects in the database is a bit different.

Our new version employs db4o's native query mechanism for locating contacts. The search criteria are still specified by name and value (as in the items() method shown previously), but we pass those objects into an implementation of the Predicate class (see Listing 3).

The Predicate class is the heart of the native query. When the query is executed, db4o repeatedly calls the match() method, passing in candidate objects fetched from the database. The match() method returns true if the candidate object satisfies the query; it returns false otherwise. Therefore, we can implement the new findContact() method within AddressBookModel (see Listing 4).

In the code shown in Listing 4, the cdQuery object is an instance of the ContactDetailQuery class. Prior to the query, we set the search criterion (with ContactDetailQuery's setSearchCriterion() method) and search value (with ContactDetailQuery's setSearchString() method). Then we call the db4o ObjectContainer's query() method, passing in cdQuery. The results object is a member of the ObjectSet class, and therefore provides us with an iterator through the collection of objects returned by the query.

The mechanism is pretty easy to figure out. db4o's native queries give us the freedom to use standard Java code as the query language for the database.

Digging Quartz
In this example, we converted an application that used a "sort of" object database to one that used a pure object database. A great deal of translation code was eliminated; instead of our having to specify the structure of the database programmatically (which, in effect is what happened whenever we placed the AggregateField and ItemField objects into the contact database), we simply told the object database which objects we wanted stored, and it stored them.

In addition, db4o's native query mechanism simplified our job of constructing queries that mimicked the original search routines. Note also that we could have easily added new queries, either by extending the match() function defined in the existing ContactDetailQuery class or by building a new implementation of Predicate with an entirely different match() method.

You can download the complete source code for this example. Simply replace the source code for the existing AddressBook application (provided with the UIQ 2.1 download), rebuild it, and run. You might want to back up the original AddressBook application source files, and you have to download the latest db4o files and place the library in a specific folder in the UIQ SDK's directory tree for the Quartz emulator to find it. There is a README file included with the source that describes these steps.

Most of all, take the time to examine db4o more closely. It's open source, so there's nothing keeping you from writing other object database applications for Quartz.

About the Author
Rick Grehan is a QA lead with Compuware's NuMega Labs. Rick has written numerous articles on Java, and—at one time—was a columnist for Java Pro. Contact Rick at [email protected].

comments powered by Disqus

Featured

  • Build Your First AI Applications with Local AI

    "AI right now feels like a vast space which can be hard to jump into," says Craig Loewen, a senior product manager at Microsoft who is helping devs unsure about making that first daunting leap.

  • On Blazor Component Reusability - From Day 0

    "We want to try to design from Day One, even Day Zero, with reusability in mind," says Blazor expert Allen Conway in imparting his expertise to an audience of hundreds in an online tech event on Tuesday.

  • Decision Tree Regression from Scratch Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of decision tree regression using the C# language. Unlike most implementations, this one does not use recursion or pointers, which makes the code easy to understand and modify.

  • Visual Studio's AI Future: Copilot .NET Upgrades and More

    At this week's Microsoft Ignite conference, the Visual Studio team showed off a future AI-powered IDE that will leverage GitHub Copilot for legacy app .NET upgrades, along with several more cutting-edge features.

  • PowerShell Gets AI-ified in 'AI Shell' Preview

    Eschewing the term "Copilot," Microsoft introduced a new AI-powered tool for PowerShell called "AI Shell," available in preview.

Subscribe on YouTube