Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Design Considerations for Using the PIM API for Java ME

 
By C. Enrique Ortiz, March 2007  

Mobile handsets such as cellphones typically have an address book to keep track of people we like to stay in touch with, a calendar to keep track of important events, and a to-do list to keep track of items we don't want to forget. This type of personal information is one of the most important functions found in a handset, just second to voice. You can use the PIM API for Java ME to enable your mobile Java applications to read and write to/from the locally stored personal information databases. You can even write synchronizers to keep your handset PIM data in-sync with remote PIM data stores. This article, part 4 of this series on using the PIM API for Java ME, covers design approaches for using the PIM API for Java ME.

Contents
 
Background
Resources
Acknowledgements
About the Author
 
Background

As covered in Part 1 of this series, Managing Personal Information - An Introduction to the PIM API for Java ME, the PIM API is contained in a single Java package javax.microedition.pim, which defines the following interfaces, classes and, exceptions:

Table 1: the javax.microedition.pim Optional Package (JSR 75)
 
 
Interfaces
Contact, ContactList, Event, EventList, PIMItem, PIMList, ToDo, and ToDoList.
Classes
PIM and RepeatRule.
Exceptions:
FieldEmptyException, FieldFullException, PIMException, and UnsupportedFieldException.
 

The PIM API is designed around the organization of the PIM data itself. PIM data is organized into databases or lists. Multiple lists can exist-for calendar events, address-book contacts, and to-do items. PIM lists contain items which are groupings of related fields. A field consists of an ID, a label, a data type, values, and attributes. This relationship is illustrated next:

Figure 1: PIM data organization
 

In this data model, PIM lists or databases are represented by interface javax.microedition.pim.PIMList and its sub-interfaces. PIM items are represented by interface javax.microedition.pim.PIMItem and its sub-interfaces. Fields are represented by the corresponding Java data type such as String, Date, int, boolean and arrays of bytes and Strings.

Design Approaches for Using the PIM API
The PIM API interface hierarchy, combined with the set of discovery methods provided by the API, gives us two main approaches to use the PIM API for Java ME. The first uses a generic approach while the second uses an explicit and specific approach. To compare these two approaches, two code snippets that are similar in functionality but different in implementation are shown. To illustrate the two approaches, two methods that load an LCDUI form with the contents of a given PIMItem are shown. Recall that to retrieve PIM items from a PIM list, use one of the PIM list items(...) methods as appropriate depending if retrieving all items, or filtering by value or category.

Generic Access to PIM Items Using PIMItem Super-Interface
The first approach is to use the PIM API totally discovery or introspection driven, where everything about the PIM databases, their fields, attributes, categories, and values are all discovered at runtime. This is the approach used in the PIMDemo example that comes with the Sun Java Wireless Toolkit. The following code snippet, adapted from the PIMDemo, implements method populateLcdUiFormFromPIMItem, which is a generic method to load an LCDUI Form from a PIMItem. This approach uses the discovery methods in PIMList and PIMItem to discover all the supported fields for the specified PIMItem, and then for each supported field, it discovers field information such as IDs, labels, data types, and values to load.

Listing 1 - Method populateLcdUiFormFromPIMItem uses generic approach to access PIM information
/**
 * Populates a LCDUI form with all fields for specified PIMItems
 *
 * @param   form                the LCDUI Form to populate
 * @param   itemCommandListener the item command listener to assign to the LCDUI items
 * @param   item                the PIMItem to popuplate from
 * @throws  PIMException        if some kind of error is encoutered while using the PIM API
 */
private void populateLcdUiFormFromPIMItem(
        Form form, 
        ItemCommandListener itemCommandListener,
        PIMItem item) 
        throws PIMException {

    // The LCDUI Form Item
    Item formItem = null;
    // Start by clearing up the LCDUI Form
    form.deleteAll();

    // Get all the supported PIM fields (IDs)for 
    // the specified PIMItem to be loaded
    int[] fields = item.getPIMList().getSupportedFields();
    // For each supported PIM field
    for (int i = 0; i < fields.length; i++) {

        // Process one field at a time
        int field = fields[i];

        // Skip CLASS fields - don't load class fields
        if ((item instanceof Contact && (field == Contact.CLASS))   ||
            (item instanceof Event   && (field == Event.CLASS))     ||
            (item instanceof ToDo    && (field == ToDo.CLASS))) {
            continue;
        }

        // Skip field with NO values to load
        if (item.countValues(field) == 0) {
            continue;
        }

        // Get the field's data type
        int dataType = item.getPIMList().getFieldDataType(field);
        // Get the field's label
        String label = item.getPIMList().getFieldLabel(field);

        // Load the PIM field based on it's data type
        switch (dataType) {

            case PIMItem.STRING: {
                String sValue = item.getString(field, 0);

                if (sValue == null) {
                    sValue = "";
                }

                int style = TextField.ANY;

                // cater for specific field styles
                if (item instanceof Contact) {
                    switch (field) {
                        case Contact.EMAIL:
                            style = TextField.EMAILADDR;
                            break;

                        case Contact.TEL:
                            style = TextField.PHONENUMBER;
                            break;

                        case Contact.URL:
                            style = TextField.URL;
                            break;
                    }
                }

                try {
                    formItem = new TextField(label, sValue, 128, style);
                } catch (IllegalArgumentException e) {
                    formItem = new TextField(label, sValue, 128, TextField.ANY);
                }
                break;
            }

            case PIMItem.BOOLEAN: {
                formItem = new StringItem(label, item.getBoolean(field, 0) ? "yes" : "no");
                break;
            }

            case PIMItem.STRING_ARRAY: {
                String[] a = item.getStringArray(field, 0);
                if (a != null) {
                    formItem = new StringItem(label, joinStringArray(a));
                }
                break;
            }

            case PIMItem.DATE: {
                long time = item.getDate(field, 0);
                int style = DateField.DATE_TIME;

                // some fields are date only, without a time.
                // correct for these fields:
                if (item instanceof Contact) {
                    switch (field) {
                        case Contact.BIRTHDAY:
                            style = DateField.DATE;
                            break;
                    }
                }

                formItem = new DateField(label, style);
                ((DateField)formItem).setDate(new Date(time));
                break;
            }

            case PIMItem.INT: {
                formItem = new TextField(
                        label, 
                        String.valueOf(item.getInt(field, 0)), 
                        64,
                        TextField.DECIMAL);
                break;
            }

            case PIMItem.BINARY: {
                byte[] data = item.getBinary(field, 0);

                if (data != null) {
                    formItem = new StringItem(label, data.length + " bytes");
                }
                break;
            }
        } // switch (dataType)

        if (formItem != null) {
            form.append(formItem);
            formItem.setItemCommandListener(itemCommandListener);
        }
    }
}
 

This approach, which uses the PIMList and PIMItem super-interfaces, results in a generic method to access all types of PIM items: Contact, Event, and ToDo. This approach also results in a smaller footprint as the same code is used to access all the different PIM item types. See the Wireless Toolkit PIMDemo project source code for more examples.

Direct Access to PIM Items Using Event, Contact, and ToDo Sub-Interfaces
The second approach relies on classes and helper methods to deal with specific PIM items such as Contact, Event, or ToDo directly. The following code snippet implements method populateLcdUiFormFromPIMEventItem, which is an Event-specific method to load an LCDUI Form from an Event PIM item. With this approach, the classes or helper methods wrap the PIM API for a specific PIM item, essentially hiding or simplifying the PIM API details. This way, you can totally abstract the PIM API for J2ME, or you can use a hybrid approach, where the application has knowledge of the PIM API while leveraging helper methods to hide or handle some of the tedious aspects of the PIM API.

Listing 2 - Method populateLcdUiFormFromPIMEventItem uses a direct approach to access PIM information, in this example, Event
/**
 * Populates a LCDUI form with all fields for specified PIMItems
 *
 * @param   form                the LCDUI Form to populate
 * @param   itemCommandListener the item command listener to assign to 
 *                              the LCDUI items
 * @param   item                the PIMItem to popuplate from
 * @throws  PIMException        if some kind of error is encoutered while using the PIM API
 */
static public void populateLcdUiFormFromPIMEventItem(
        Form form, 
        ItemCommandListener itemCommandListener,
        Event item) 
        throws PIMException {            

    // The LCDUI Form Item
    Item[] formItem = new Item[6]; // array for form items of interest
    // Start by clearing up the LCDUI Form
    form.deleteAll();
    // Get the list owner of this event
    EventList eventList = (EventList) item.getPIMList();

    // Retrieve the Event.START field
    formItem[0] = newDateFieldFromPimItemField(
            item, Event.START, 0, DateField.DATE_TIME);

    // Retrieve the Event.END field
    formItem[1] = newDateFieldFromPimItemField(
            item, Event.END, 0, DateField.DATE_TIME);

    // Retrieve the Event.SUMMARY field
    formItem[2] = newTextFieldFromPimItemField(
            item, Event.SUMMARY, 0, TextField.ANY);

    // Retrieve the Event.LOCATION field
    formItem[3] = newTextFieldFromPimItemField(
            item, Event.LOCATION, 0, TextField.ANY);

    // Retrieve the Event.NOTE field
    formItem[4] = newTextFieldFromPimItemField(
            item, Event.NOTE, 0, TextField.ANY);

    // Retrieve the Event.ALARM field
    formItem[5] = newIntTextFieldFromPimItemField(
            item, Event.ALARM, 0, TextField.DECIMAL);

    formAppendItem(form, itemCommandListener, formItem);
}

//////////////////////////////////////////////////////////////////////
// Helper Methods
//////////////////////////////////////////////////////////////////////

/**
 * Appends the specified LCDUI Items into the specified Form. This 
 * methods checks if Item is valid (non-null), but assumes the Form is 
 * valid.
 *
 * @param form                  the LCDUI Form to append into
 * @param itemCommandListener   the command listener to set
 * @param formItem              the LCDUI Item array to append
 */
private static void formAppendItem(
        Form form, 
        ItemCommandListener itemCommandListener, 
        Item[] formItem) {
    if (formItem != null) {
        for (int i=0; i<formItem.length; i++) {
            if (formItem[i] != null) {
                form.append(formItem[i]);
                formItem[i].setItemCommandListener(itemCommandListener);
            }
        }
    }
}

/**
 * Creates an LCDUI TextField from the specified PIM item and field
 *
 * @param       item          the PIM Item to load from 
 * @param       fieldID       the PIM field to load from 
 * @param       valueIndex    the index of the PIM field value to load
 * @param       style         the TextField style to use
 * @returns                   the LCDUI TextItem or null
 */
private static Item newTextFieldFromPimItemField(
        PIMItem item, 
        int fieldId, 
        int valueIndex,
        int style) {
    Item formItem = null;
    if (item.getPIMList().isSupportedField(fieldId)) { // supported ?
        if (item.countValues(fieldId) > 0) { // with values?
            String label = item.getPIMList().getFieldLabel(fieldId);
            String value = item.getString(fieldId, valueIndex);
            if (value == null) {
                value = "";
            }
            try {
                formItem = new TextField(label, value, value.length(), style);
            } catch (IllegalArgumentException e) {
                formItem = new TextField(label, value, 128, TextField.ANY);
            }
        }
    }
    return formItem;
}

/**
 * Creates an LCDUI TextField from the specified PIM item and field
 *
 * @param       item          the PIM Item to load from 
 * @param       fieldID       the PIM field to load from 
 * @param       valueIndex    the index of the PIM field value to load
 * @param       style         the TextField style to use
 * @returns                   the LCDUI TextItem or null
 */
private static Item newDateFieldFromPimItemField(
        PIMItem item, 
        int fieldId, 
        int valueIndex,
        int style) {
    Item formItem = null;
    if (item.getPIMList().isSupportedField(fieldId)) { // supported ?
        if (item.countValues(fieldId) > 0) { // with values?
            long time = item.getDate(fieldId, valueIndex);
            Date value = new Date(time);
            // Get the field's label
            String label = item.getPIMList().getFieldLabel(fieldId);
            formItem = new DateField(label, style);
            ((DateField)formItem).setDate(value);            
        }
    }
    return formItem;
}

/**
 * Creates an LCDUI TextField from the specified PIM item and int field
 *
 * @param       item          the PIM Item to load from 
 * @param       fieldID       the PIM field to load from 
 * @param       valueIndex    the index of the PIM field value to load 
 * @param       style         the TextField style to use
 * @returns                   the LCDUI TextItem or null
 */
private static Item newIntTextFieldFromPimItemField(
        PIMItem item, 
        int fieldId, 
        int valueIndex,
        int style) {
    Item formItem = null;
    if (item.getPIMList().isSupportedField(fieldId)) { // supported ?
        if (item.countValues(fieldId) > 0) { // with values?
            String label = item.getPIMList().getFieldLabel(fieldId);
            int value = item.getInt(fieldId, valueIndex);
            formItem = new TextField(
                    label, 
                    String.valueOf(value), 
                    64,
                    style);
        }
    }
    return formItem;
}
 

This approach, which consists of direct or explicit access via a specific PIMItem sub-interface, in this example Event, is combined with helper methods that implement common functionality. It gives the developer control on the specific fields to manipulate for a given PIM item type. The footprint is a bit larger than the generic approach previously explained; it's specific to a PIM item type, and the core logic found in method populateLcdUiFormFromPIMEventItem has to be repeated as appropriate for each PIM item of interest. But the footprint of this approach is still small enough due to the use of common helper methods that are applicable across PIM items.

Typical Steps for Using the PIM API
Regardless of the design approach that is taken, the steps to manipulate the PIM data are very consistent, as illustrated in the flowchart below:

Figure 2 - Flowchart depicting the typical steps when using the PIM API for Java ME
 

It all starts with selecting and opening the PIM databases of interest, and for each database, searching and retrieving the PIM items of interest, and for each PIM item, retrieving the fields' attributes and values of interest.

Portability Considerations
Not all PIM API implementations support all the PIM lists, fields, and categories specified in the PIM API for Java ME specification (JSR 75). Regardless of the approach used to access the PIM data, before referencing PIM lists, fields, and categories, the application should test if those are supported, otherwise an exception might be thrown. See Part 2 of this PIM API series, Portability Considerations, for more information.

Resources
Acknowledgements

Many thanks to Kevin Lym, Vasily Isaenko, and Alexander Glasman for all their feedback and helping improve this article.

About the Author

C. Enrique Ortiz is a mobile technologist, software architect and developer, and a writer and blogger. He has been author or co-author of many publications, and is an active participant in the Java mobility community and in various Java ME Expert Groups. Enrique holds a B.S. in Computer Science from the University of Puerto Rico and has more than 17 years of software engineering, product development, and management experience.