pimAL
Construction and Development of an Abstraction Layer for Personal Information Management
written by
Kathrin Braunwarth
Gilbert Fridgen?
Fabian Kröher
Table of Contents
1. Introduction
The following paper gives an overview of the pimAL framework written for SuperWaba.
1.1 Problem
Personal information data is nowadays stored on many devices running on several different platforms, each with a choice of applications. There are generally accepted formats for personal information data like vCard and vCalendar, but many applications do not implement these formats. Memory on mobile devices is often too scarce, compatibility not appropriate or some other reason. Instead they store the data in their own, special way. Because of these different implementations, the concurrent use of applications from different manufacturers is very inconvenient. Synchronization is particularly hard to achieve.
Each platform is specialized on a specific application area. Pocket PC devices, for example, are often designed for multi-media based use. In contrast, the focus of the Palm platform is handling personal information data. Companies wishing to equip their employees with mobile devices have to decide whether they either implement a homogeneous infrastructure, and give all employees devices of the same platform, or a heterogeneous infrastructure, and choose the right device for everyone depending on his demands. In a homogeneous infrastructure you have to support only one platform, but there is danger of lock-in and dependency on one manufacturer, which increases the risk of uncontrollable cast-growth in the future.
In a heterogeneous infrastructure the company gets used to supporting different platforms and the switch to devices of another manufacturer is always possible, with little effort. In return, every application has to be developed and supported for each platform. Wouldn't it be great if software developers didn't have to care about the differences between the individual platforms? A platform independent technology like SuperWaba provides a runtime environment (very similar to a Java runtime environment on a desktop PC) for applications on a variety of devices. But some problems still remain, and one of them is the access to personal information management (PIM) data on miscellaneous platforms out of SuperWaba.
1.2 Purpose
The pimAL framework accomplishes a mapping of the data structures and provides access to PIM applications out of the SuperWaba runtime environment on different hardware and software platforms. By now the framework supports the Palm and the PocketPC platform (as does SuperWaba). Expansion to other platforms (like Symbian) has been considered in the current version.
The purpose of this paper is to deliver users of the pimAL framework an insight of the architecture and describes how to use the pimAL framework.
1.3 Approach
To fulfill this purpose chapter 2 describes the basic architecture of the pimAL framework. Furthermore you will see some relevant details of the respective platform-dependent layers of pimAL.
Subsequently in chapter 3 you will find a tutorial that will guide you how to use the pimAL framework by showing relevant cases.
This paper concludes with some thoughts about the applicability and future of the pimAL framework.
2 Architecture
The first part of this chapter gives a review of the basic architecture; the pimAL framework is built upon. Sections 2.2 and 2.3 will then address the device-specific implementations for Palm and Pocket PC handhelds.
2.1 Basic Architecture
The basic architecture of the pimAL framework is shown below in figure 1. This diagram shows the only classes an application, which uses this framework, has to cope with. As you can see, the first class it connects to is PIMFactory. This abstract class uses the design pattern Singleton and thus only allows one instance of it being created.
Figure 1. Architecture of the pimAL framework
However even more important is the fact, that PIMFactory uses the design pattern Factory and thus hides device-specific implementations. As you can see in figure 2, a client application calling the method getInstance() will not only receive a previously requested instance if there exists one (Singleton), but will also receive a device-specific implementation without knowing anything about it. The getInstance() method first checks, if there already exists an instance, and if not, it creates one of the subclasses according to the device it is running on.
Figure 2. Support for different platforms by the pimAL framework
Having an object of class PIMFactory, an application can access the device's PIM data by using the corresponding create method, e.g. createAddressBook(). It will then receive an object of one of the *Book classes (where * is one of "Address", "Date", "ToDo" or "Memo") or more accurately an object of an unknown device-specific class, that e.g. implements the interface AddressBook. Saved *Records can be requested from the book. A record is one data set of the book, e.g. the data of one person or company in the address book. The *Fields contain the actual data and can be requested from the record. In our example, you could request the AddressField for the telephone number from the AddressRecord you're interested in. The abstract superclasses VCardField, VCalField and VersitField simply contain methods and constants that are useful for all of the derived classes.
Most important about this architecture is, that an application using pimAL (and its programmer) never has to know the device-specific implementation. It always "talks" to abstract classes, interfaces, or classes that aren't device-specific.
Knowing the basic architecture, we can now have a look at more specific characteristics of the pimAL framework. Section 2.1.1 will show, how the platform's different data structures and formats can be brought together. Section 2.1.2 shows a way to handle data that simply isn't storable on a specific device.
2.1.1 A Common Data Format for All Devices
When trying to save PIM data on different kinds of devices, there are basically two problems: In the first place, different devices have different capabilities of what and how much to save; in the second place, different devices tend to save same things in different ways. To show you this problem, let's have a look at the different data structures for electronic contact information on Palm and Pocket PC handhelds in figures 3 and 4.
Figure 3. Entity Relationship Diagram of the Palm’s way of storing electronic contact data
Figure 4. Entity Relationship Diagram of the PocketPC’s way of storing electronic contact data
As mentioned in the introduction, there are nowadays common formats for PIM data, that try to integrate all these different formats and structures: vCard and vCalendar. Because of this, the *Fields mentioned in 2.1 are in their structure and variety similar to these standards. Figure 5 e.g. shows the structure, a vCard stores address data in.
Figure 5. Entity Relationship Diagram of the vCard’s way of storing electronic contact data
A vCard or vCalendar file basically consists of pure text. Every line in a vCard or vCalendar file contains one chunk of contact information and has a certain structure: First is always a key, which tells what kind of information is stored in this line. Second are options for that specific key that e.g. can tell you, what encoding the data has. Third is the actual value.
Semicolons separate the key and options; options consist of a key/value pair. The value follows separated by a colon. Semicolons internally separate a multipart value again. This would formally look like this:
KEY *[";"OPTION_KEY"="OPTION_VALUE] ":" +(";"VALUE)
An example line from the vCard standard looks like this:
ADR;type=WORK;type=PREF:;;Park Avenue 17;Augsburg;;86159;Germany
As mentioned before, the *Fields' internal structure is very similar to vCards' respective vCalendars'. Their basic structure with methods for reading and writing the key, options and values is defined in the class VersitField. VCardField and VCalField are derived from VersitField and mainly contain constants for the keys, the respective standard uses. In addition they contain useful methods for converting data formats (e.g. waba.sys.Time to a string according to ISO8601 and vice versa) or for scoring the similarity of two fields. The derived classes AddressField, DateField, ToDoField and MemoField are mostly empty but can contain additional tools or information.
Creating one of these "Fields" is very similar to writing a line of vCard or vCalendar. The constructor is parameterized with
- the key, as one of the integer constants of VCardField or VCalendarField. These constants bear the same name as the keys in vCard a vCalendar.
- the options, as a String[], e.g. {"type=WORK" , "type=PREF"}, again according to vCard and vCalendar.
- the values, as a String[], e.g. {"", "", "Park Avenue 17", "Augsburg", "", "86159", "Germany"}, in sequence an content identical to the vCard and vCalendar standards.
These fields can then be passed to an AddressRecord, which stores them. Section 3.1 will show how this exactly works. The following section 2.1.2 will address another interesting question: What happens to address data that cannot be saved on a specific device?
2.1.2 Handling of Not Supported Data
There's a concept to handle data that cannot be saved in the original application on a specific device. This data are so called "not supported fields". The pimAL framework provides the use of so called NotSupported Handlers, whom you can tell what should happen to these fields. Figure 6 shows you the architecture of this handling as implemented in the pimAL framework. You can also see our provided handler, the NotSupportedHandlerNote as an example.
Figure 6. Architecture for handling of not supported fields
For using the NotSupportedHandler you have to register it at your respective *Record. The records will pass their not supported fields to the write( ) method of their NotSupportedHandler. Therefore each NotSupportedHandler needs to implement the respective interface of package pimal.notsupportedhandler. These interfaces demand a write( ) and a complete( ) method.
A new kind of NotSupportedHandler should be placed into a new package in pimal.notsupportedhandler. This package will probably contain a superclass for the different implementation of the interfaces, which provides some functionality needed by all notSupportedHandlers of this kind.
As we now know the basic architecture of the pimal framework, we can have a look at the device specific implementations for the framework. In 2.2 we will have a closer look at the implementation for Palm handhelds. 2.3 will show you, how the PIM data is accessed on PocketPCs.
2.2 Support for Palm
Since there already exist classes to access Palm PIM data, the only thing to do is to map the Palm data to vCard fields. As shown in figure 2, this means to implement the interfaces for *Books and *Records as well as to derive a PalmPIMFactory, that returns the Palm specific implementations when asked to create *Books, which is rather easy.
Writing the specific implementations for *Books means to write classes, that can access the PIM databases to return, create or delete records and categories. As mentioned before, there already exist such classes, which means, that the main job to do is to wrap their method signatures.
Writing the specific implementation for *Records is more complicated. Basically you'll have to write two methods: One method, that retrieves the data from the device and writes it into the fields it returns, and one method, that parses fields and writes them back to the device. To give you an example of how complicate this could be, just imagine recurrence rules for appointments. Rules like "last Friday of every month" can be saved quite differently (and difficultly) on miscellaneous devices.
2.3 Support for Pocket PC
This section explains how PIM data is read from the PocketPC and how it is mapped to and from the fields of the pimAL architecture. If we use expression "PocketPC" in this paper we mean all platforms which run WindowsCE (i.e. H/PC, PocketPC, PocketPC 2002, ...).
Since there did not exist class’s respective native code to access PocketPC data, first a platform-dependent native layer that provides the PIM data for SuperWaba has to be created (it is called pimal.dll from here on). Afterwards a platform-dependent layer in SuperWaba had to be written which represents the data structure of a PocketPC device (it is called POOM wrapper layer henceforth). After that, the pimAL framework *Record and *Book class interfaces could be implemented for the PocketPC device which map the native data structure to the pimAL data structure (very similar to section 2.2).
Section 2.3.1 summarizes how the native layer pimal.dll has been created and provides links to further information, section 2.3.2 and 2.3.3 describe the POOM wrapper layer. Sections 2.3.4 and 2.3.5 deal with the mapping of native data structures of a PocketPC to the pimAL framework and are marked as excursuses thus they are not compulsory to understand when using pimAL.
2.3.1 How to access PIM data on a PocketPC device
The PIM data on a Pocket PC device can be accessed by using the POOM (Pocket Outlook Object Model) interface. POOM is an embedded Visual C++ API, which represents a subset of the OOM (Outlook Object Model) which can be used for accessing PIM data on a desktop PC.
The library functions of POOM are implemented in pimstore.dll, which can be found in the WINDOWS-folder on the Pocket PC device. The corresponding header file is named pimstore.h. There are two "normal" ways to use the POOM: The first is to include the pimstore.h header file in his own embedded Visual C++ Application, the second is to load the pimstore.dll in embedded Visual Basic and call the functions from there.
Nitin Kumar Garg (
nitin@unitechgroup.com) has published a very good example (article, sourcecode and demo application) for the first possibility at codeguru.com:
http://www.codeguru.com/ce/poom.html.
Further the MSDN POOM API documentation can be helpful:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcepoom/html/ceconPocketOutlookObjectModelReference.asp (no idea why, but the link keeps changing all the time; you can also find this page by searching MSDN with keywords "Pocket Outlook Object Model Reference").
The second possibility (Embedded Visual Basic access) will not be a topic in this paper; links to further information (embedded Visual Basic is to some degree helpful for creating quick and dirty testing applications) are:
MSDN POOM API Code Examples:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wcepoom/html/ceconPocketOutlookObjectModelCodeExamples.asp (search for "Pocket Outlook Object Model Code Example’s if the link does not work).
Use eVB (embedded Visual Basic) to access the Pocket Outlook Object Model (POOM) on your PocketPC (by Derek Mitchell):
http://www.devbuzz.com/content/zinc_evb_and_poom_pg1.asp.
For both described possibilities you will need to download and install the embedded Visual Tools for PocketPC (they can be downloaded freely at
http://msdn.microsoft.com/library/default.asp?url=/downloads/list/pocket2002.asp). Another helpful article for this might be "Introduction to Pocket PC Programming" at codinggeeks:
http://www.codinggeeks.com/modules.php?name=News&file=article&sid=11.
Accessing the POOM from SuperWaba is not a "normal" way and needed some more development to be done. Since SuperWaba SDK version 3.6 it is generally possible to access native libraries not only on Palm, but also on PocketPC devices. This means, there is a way to call functions from an embedded Visual C++ DLL from SuperWaba. How this can be done is described in the SuperWaba Library Tutorial, which can be bought at www.superwaba.org.
But this is not as easy as it actually sounds: An embedded Visual C++ DLL can only be accessed from SuperWaba when it has been specially developed for this purpose - thus, accessing the pimstore.dll library directly does not work. Instead, another DLL had to be developed which wraps the pimstore.dll library functions for accessing them from out of SuperWaba. We called this DLL pimal.dll; it has to be installed (which means copied into to Windows directory) on every device where the POOM Wrapper layer (see 2.3.2) is used - since SuperWaba 3.6 and above will contain the DLL in their installation routines no more work has to be done by the user.
Some useful information how to build an embedded Visual C++ DLL can be found in this codinggeeks article:
http://www.codinggeeks.com/modules.php?name=News&file=article&sid=13.
How to configure the DLL to make it accessible from SuperWaba and how to handle parameters and return values can be found in the SuperWaba Library Tutorial.
The PIM datasets, which are returned by pimal.dll, are generally Strings with different delimiters. These delimiters are defined as constants in the pimal.dll workspace in StdAfx.h.
Currently the following delimiters are used: Datasets are delimited by the String "|~~|" (without the ""); within a dataset, the different fields are delimited by "|1~". Within a field, different values (when they exist) are delimited by "|2~" and within these (if there is more than one value), values are delimited by "|3~". Examples (only for the use of delimiters, please do not take this as an example for order and value of the described fields):
45|~~|87|~~|98 represents three datasets with only the ids as fields.
45|1~Duck|1~Donald|~~|87|1~Duck|1~Daisy|~~|98|1~Duck|1~Dagobert represents three datasets with ids, last names and first names.
46|1~urgent meeting with bill|1~steve|3~steve@apple.com|2~bill|3~bill@microsoft.com|1~very important represents one dataset with an id field, a subject field, a recipient field and an importance field. The recipient field is divided into two recipients, (by "|2~") which both contain a name ("bill" and "steve") and an email address (divided by "|3~").
The POOM wrapper layer classes IContact, IAppointment and ITask are able to parse these "natively" generated Strings and transfer them back into the corresponding fields.
2.3.2 The POOM Wrapper Layer
This layer wraps the POOM in SuperWaba (as far as it is implemented by the pimal.dll interface). All the classes belonging to this layer reside in the *.pocketpc.builtin package. Since these classes talk to native code on the PocketPC device and the calling of native methods from SuperWaba needs a lot of processing power, it is important to use them carefully and only call the save(), refresh(), ... methods when they are really needed. Otherwise your SuperWaba application may become very slow and/or drain the battery rapidly.
The following image shows all the classes of the POOM wrapper layer in an overview.
Figure 7. Architecture of POOM wrapper layer
In the next section, a short overview of all the classes will be given.
2.3.2.1 IObject, IAppointment, IContact, ITask
The classes IAppointment, IContact and ITask are core classes of this layer and represent the data types Appointment, Contact and Task on the PocketPC device. They are all derived from IObject, which is capable of transferring return values (Strings) from the native calls to pimal.dll into the corresponding data types. Further, access to all the fields of these classes is provided via the different get-Methods (the name of the field that will be accessed must be given). Internally, the fields and their field names are stored in a Hashtable. The field names and the order in which the fields are read out of the native String are arranged in class Constant in the attributes iAppointmentFields, iContactFields and iTaskFields (which are Vectors of Strings). Also see 2.3.4 and 2.3.5 on this issue.
Further, these classes contain refresh(), save() and delete()-Methods which will refresh the data of the IObject from the device, save the IObject's data on the device and delete the IObject on the device.
2.3.2.2 IObjects, IAppointments, IContacts, ITasks
These classes represent collections of objects of the classes IObject, IAppointment, IContact and ITask. The objects are stored into a Vector and can be accessed by the i*At( )-Methods.
2.3.2.3 IPOutlookItemCollection
The IPOutlookItemCollection contains most of the native method calls; a complete list of e.g. all the Contacts on the device can be created here as well as new, empty objects of the classes IAppointment, IContact and ITask, which then can then be filled with data.
2.3.2.4 IDate, IRecipient, IRecipients and IRecurrencePattern
These classes represent the corresponding POOM interfaces and are mainly used to transfer them into one single String and back. IRecurrencePattern and IDate are both capable of mapping their POOM data types to valid vCard/vCal representations.
2.3.2.5 Constant
Holds different constant values (like delimiters used in the native String), the native field names, the order of reading them out of the native String and the i*FieldTemplates which will be further described in sections 2.3.4 and 2.3.5.
2.3.3 Using the POOM wrapper layer directly
It can be useful to use the POOM wrapper layer directly from out of SuperWaba when only working with Pocket PC devices (but then, why would you use SuperWaba at all? Of course because it's great anyway!). The POOM wrapper layer provides direct access to the POOM and thus makes accessing the PIM data more transparent and less complex.
2.3.4 (Excursus) Mapping from the POOM wrapper layer to the pimAL framework ("upward") or why not using the POOM wrapper layer directly
Understanding of this section is not required for using the pimAL framework with your application. It provides, however, a deeper understanding of how values are mapped from the POOM layer to the pimAL data structure and how easily this mapping can be changed or improved.
As mentioned before, the pimAL framework has been implemented to be able to access the PIM data platform independently. This interface sits on top of the POOM wrapper classes and maps their attributes to the generic vCalendar and vCard data structures of the pimAL framework. This takes slightly more processing power for the sake of platform independence. Platform indecency in this case does not only mean independence of different programming languages/dialects but also (and this is very important for PocketPC) of the platform dependent data structures of the PIM data. In fact, it is a greater problem to make the data structure of a PocketPC platform-independent than it is to access platform-dependent (native) code on these machines. To be independent of the horrible PocketPC data structures and of possible future changes, it is highly recommended to use the pimAL framework to manipulate PIM data, even if you develop exclusively for Pocket PC.
To map the data types of the POOM wrapper layer to the pimAL framework, template-Vectors of AddressFields (for mapping IContact to PocketPCAddressRecord), DateFields (for mapping IAppointment to PocketPCDateRecord) and ToDoFields (for mapping ITask to PocketPCToDo) are used. To create these templates, the init*FieldTemplates( ) of class Constant are used. As example, we will refer to the first AddressField, which is created as a template in the initAddressFieldTemplates:
According to the pimAL architecture, AddressFields with names (corresponding to the vCard property N) contain an array of values of length 5 with the last Name at position 0, first Name at position 1, middle Name at position 2, title (like Dr.) at position 3 and name suffixes at position 4. The option array of an AddressField, which stores the name, is left empty (no options). The trick is now, that you don't store values in the value array of this AddressField but field names of IContact fields - this makes the created AddressField a template (i.e. "(String)lastName" is stored at position 0 of the value array).
"Upward"-mapping of an IContact, IAppointment or ITask object happens, when a new PocketPCAddressRecord, PocketPCDateRecord or PocketPCToDoRecord is created with the IContact, IAppointment or ITask as a source and then its get*Fields()-method is called. This method is (finally) implemented in the class PocketPCRecord and only called by the subclasses IContact, IAppointment or ITask. What happens now (in the getFields()-method of PocketPCRecord) is, that first a copy of the corresponding template-Vector is retained from the class Constant. To get back to our example, when mapping an IContact to a PocketPCAddressRecord, the getFields()-method from PocketPCRecord gets a copy from the AddressField-templates of the class Constant by calling the templates() and template(int i)-methods of the subclass PocketPCAddressRecord (which call iAddressFieldTemplates() and iAddressFieldTemplate(int i) of the class Constant). Then it replaces all the field names of the templates with their values by looking them up in the IContact. So, when the first AddressField-template is touched, the first value will be "(String)lastName". This value (which is in fact a field name) is used with getValue(String fieldname) of the IContact object which will return the true value for the field name "(String)lastName". This true value will be inserted into the AddressField template where the field name has been stored: In our example "(String)fieldname" at position 0 in the value array of the AddressField template which represents the vCard N property will be replaced with e.g. "Duck" (if the IContact is the one of Donald Duck ...). This process will go on for every value of every AddressField template in the addressFieldTemplates Vector of the class Constant. Finally all the field names which had been stored in the AddressField templates will be replaced with the specific values of the one IContact which is the source of the PocketPCAddressRecord where getAddressFields() had been called. The AddressField templates (which are no more templates but a specific dataset) will be returned by this method.
The reason for mapping with templates is, that it is the most flexible solution - the POOM Wrapper layer in SuperWaba can easily adapt changes within POOM.
2.3.5 (Excursus) Mapping from the pimAL framework to the POOM wrapper layer ("downward")
Understanding of this section is not required for using the pimAL framework with your application. It provides, however, a deeper understanding of how the values from the pimAL data structure are mapped to the POOM wrapper layer objects and how easily this mapping can be changed or improved.
As well as for mapping "upward" the templates of the class Constant can be used for mapping data in the other direction - only one problem has to be solved: If somebody does not exactly use the same AddressFields, DateFields or ToDoFields as they are in the template Vector, the data will not be mapped. Another example: Somebody has created an AddressField.TEL with the options HOME and PREF. If you look at the AddressField templates, you will only find AddressField.TEL with HOME, PREF and VOICE as options - so if one only maps exactly matching templates, this AddressField will not be considered. Therefore, it is necessary to determine to which degree AddressFields (VersitFields) match and then map a given field to the best matching template. The method match( ) of class VersitField has been implemented for this purpose; it returns a score which indicates how exactly the compared VersitFields match. If the key of the fields is not the same (e.g. comparing an AddressField.TEL with an AddressField.N) the score will always be 0, which means, "No match - cannot be mapped to each other". If the keys match, scores will be augmented for every options both of the fields have. Matching options HOME and WORK will be assigned a higher score than matching options like PREF, VOICE or FAX. This means, that it is more possible that a TEL;HOME;VOICE will be mapped to a TEL;HOME;FAX than to a TEL;WORK;VOICE, because we considered it more important to keep HOME and WORK separated - even if a VOICE number is stored as a FAX number. When comparing X-fields, the first option (which contains the name of the X-field, e.g. X-SPOUSE) has to be identical; otherwise the matching score will be 0.
The mapping (which takes place in the method setFields( ) of PocketPCRecord and thus is inherited by PocketPCAddressRecord, PocketPCDateRecord and PocketPCToDoRecord) now works like this: First, all the fields that contain the option PREF are considered (they are mapped first - that means they have a higher possibility to get the better matching templates) than all the others. For every given field the best matching template field is found, and its values are saved in an IContact, IAppointment or ITask with the field names from the template. After that, the used template is deleted (otherwise the fields contained by the template could be overwritten, if the template also matches to another of the given fields). The more of the given values are mapped, the fewer templates will be left and the possibility, that a given field does not find a matching template (score of 0) any more increases. All the given fields that do not match to a template (either because there exists no matching template or the matching template has been used for another field before) will be passed to any registered NotSupportedHandler where the programmer can decide what to do with them (e.g. store them in the note). Therefore also read chapter 2.1.2 or 3.2.
This way, it is ensured that as much as possible of the given AddressFields, DateFields or ToDoFields are stored properly on the device (more exactly: as properly as possible) and as few as possible are handed over to the NotSupportedHandler. Further, fields with the option PREF will be mapped "better" than any others - the possibility that they will be stored in the best matching fields is higher than for any other field.
This chapter explained the basic design of the pimAL platform-dependent and platform independent components. It is absolutely necessary to understand these basic facts when using pimAL with your application. More detailed information can be found in the sourcecode/javadoc documentation of the pimal.* packages within SuperWaba.
3 Tutorial
This part of the paper should guide you through the pimAL framework and explain how to use it. To avoid frequently asked questions, the structure of this chapter is arranged in cases that describe common situations a programmer can be faced with.
3.1 Basics
The basic needs when working with the pimAL framework will be addressed in this chapter: Reading, adding, changing and deleting PIM data. For simplicity the address book will be used as an example. The specific method signatures and names will not be a topic. Please refer to the javadoc documentation for these information.
As mentioned before, an application first contacts the PIMFactory and receives an device-specific implementation without knowing about it. Through the methods "create*Book()" the application can get access to the PIM data. Receiving e.g. an AddressBook, the application can request, create or delete AddressRecords and request, add, remove or rename categories. Having requested an AddressRecord, the application can read a Vector of all AddressFields and then read the data from the fields.
Objects of class AddressField can easily be changed through their methods. Changes will not be written directly to the device, but writing the Vector of AddressFields back to the AddressRecord (via setAddressFields()) will make the changes persistent. To add an AddressField, create it and add it to the Vector, before writing the Vector back. To remove an AddressField, simply remove it from the Vector, before writing it back.
To add an AddressRecord, the corresponding method of AddressBook has to be called. An empty AddressRecord will be returned, which has to be filled with AddressFields.
To delete an AddressRecord, again, call the corresponding method of AddressBook. Deleting an AddressRecord will set its entry in previously returned Vectors (via getAddressRecords()) to null.
All these rules also apply to the respective classes for Dates, ToDos and Memos.
3.2 Handling of data which is not supported by a device
One of the main problems of using different platforms is the different data structure. Therefore the pimAL framework provides the construct of so called NotSupportedHandlers for AddressRecords, DateRecords and ToDoRecords. In this chapter you will get a short instruction how to use the NotSupportedHandlers.
3.2.1 How to use a NotSupportedHandler
Using of a NotSupportedHandler is rather easy: The only thing to do is to register the favored handler at the *Record you're currently working with using it's method registerNotSupportedHandler( ).
3.2.2 How the NotSupportedNoteHandler works
You have to collect the fields of a Record (AddressRecord, DateRecord or ToDoRecord) that could for some reason not be supported by an application into a vector. For handling these not supported fields just give this Vector of AddressFields, DateFields or ToDoFields as a parameter to the method write( ) of your respective NSHNote (your NotSupportedHandlerNote), in our case, the AddressNSHNote, the DateNSHNote or the ToDoNSHNote. The method write( ) attaches to the actual note "#### DO NOT EDIT BELOW ####" and afterwards the not supported fields a notation you know from vCard respectively from vCalendar. Thus advanced users can edit the fields even the device does originally not support them.
Vice versa the method complete( ) reads the field note (from vCard) respectively description (from vCalendar) and parses the text for the not supported fields. These methods use the divideIntoNoteAndFields() to divide the actual note from the not supported fields and divideFieldIntoKeyOptionsValues(), which divides the String of one field into a String object, that represents the key, and to Vectors that represent the options and the values, from the superclass NotSupportedHandlerNote. Afterwards a new AddressField, DateField or ToDoField is created with the key, the options and the values of each not supported field and all together sent back as vector.
3.2.3 How to write a new handler
For writing a new handler for not supported fields your new class has to implement the respective interface of package pimal.notsupportedhandler (AddressNotSupportedHandler, DateNotSupportedHandler or ToDoNOtSupportedHandler). Create a new package for the new handler in pimal.notsupportedhandler. (For the notSupportedNoteHandlers it is pimal.notsupportedhandler.note) In this new package create a new superclass for the new NotSupportedHandlers. Then write your actual handlers for AddressFields, DateFields and ToDoFields that all have to extend from this superclass. You find an overview of this architecture in chapter 2.1.2.
Every concrete handler needs at least these two methods:
public void write( ):
In this method you have to tell what should happen to the fields collected in the Vector of not supported fields sent by the respective Record.
public Vector complete( )
Here you have to provide a possibility to return the not supported fields when the record is read out.
3.3 Extending pimAL
The following sections describe means to extend the pimAL framework to support a new platform (as soon as SuperWaba is extended again), or to support more features on the existing platform.
3.3.1 How to support a new platform
On the SuperWaba side, support for a new platform is very simple: First, you have to create two new packages (like
.pimal.symbian and *.pimal.symbian.builtin). Put all your classes that talk to native code into the *.pimal..builtin package as well as the classes that do the mapping of the platform-dependent data structure to that of the pimAL framework. The native data structure is represented in SuperWaba and then mapped to the pimAL framework. This design has the advantage that mapping is transparent for the developer and it is possible to manipulate the native data structure directly when needed (although this contradicts the purpose of pimAL).
The
.pimal..builtin classes represent these platform-dependent structures. In pimal.pocketpc and pimal.palm these layer are mapped to the pimAL framework (by implementing the pimAL interfaces).
How extensive the platform-dependent layer is implemented when developing support for new platforms is up to the developer. Generally it might be better to implement the intermediate layer more substantial if differences between native data structure and pimAL data structure are quite distinct; on the other hand you can spare yourself the work, if native data structure and pimAL data structure differ only slightly.
Next, you need to fill the
.pimal. package with classes: First, create a *PIMFactory (e.g. SymbianPIMFactory) which implements *.pimal.PIMFactory. This will require you to implement four methods: createAddressBook(), createMemoBook(), createToDoBook() and createDateBook(). These Methods have to return instances of your *AddressBook, *MemoBook, *ToDoBook and *DateBook classes (e.g. SymbianAddressBook, SymbianMemoBook, SymbianToDoBook and SymbianDateBook) which should be created next. These classes (referred to as the *Book classes in the following) have to implement one of the following interfaces: *.pimal.addressbook.AddressBook, *.pimal.memobook.MemoBook, *.pimal.todobook.ToDoBook and *.pimal.datebook.DateBook. If your device does not support all the four data types (e.g. POOM provides no access to the memos), throw a NotSupportedByDeviceException.
Implementing these classes in your *Book classes will require to create the following methods in each of them:
(1)
addCategory( )
getCategories()
removeCategory( )
renameCategory( )
(2)
create*Record()
delete*Record( )
get*Records()
The methods shown under (1) are for dealing with categories on the device (add, get, remove and rename them). The methods under (2) are for creating, deleting and getting the *Records of the device.
There are two important things to bear in mind when implementing create*Record(), delete*Record( ) and get*Records(). To ensure that e.g. a *Record is deleted from every Vector that has been returned from get*Records(), only one single Vector object must be passed back and forth from the *Book classes (we will call this Vector the return-Vector from now on). That means, that a *Book class must store a reference to the Vector it returns; this way it can alter it instantly when a *Record is deleted or created. To ensure that the Vector stays the same when get*Records() (which actually reads all the *Records from the device) is called, it is important not to delete the stored Vector and create a new one, but to simply clear() it and then fill it with the newly read out data.
The method create*Record() returns an instance of a *Record (and adds it to the return-Vector) which you should implement next; these classes have to implement one of the following interfaces: *.pimal.addressbook.AddressRecord, *.pimal.memobook.MemoRecord, *.pimal.todobook.ToDoRecord and *.pimal.datebook.DateRecord; deleteRecord( ) deletes a *Record on the device (and from the return-Vector) and get*Records() returns instances for all the datasets which are stored on the device in the return-Vector (before that the return-Vector is cleared as mentioned above). Please do not read out the full data from the device when get*Records() is called because it can slow down this single call significantly; only read out the ids at this point and create (except for the id) empty *Record objects.
Implementing the *Record interfaces will require you to create the following methods in each of your *Record classes:
(3)
rawReadNote()
rawWriteNote( )
(4)
get*Fields()
set*Fields( )
The methods under (3) are for manipulating the note of the *Record directly on the device. Lets take a look at the methods under (4): get*Fields() must return a Vector of either *.pimal.addressbook.AddressFields, *.pimal.datebook.DateFields, *.pimal.todobook.ToDoFields, *.pimal.memobook.MemoFields. When get*Fields() is called, the data has to be read out directly from the device, new *Fields have to be created from it and stored in a Vector. This is the place where you need some kind of "upward"-mapping of the data types (mapping the native data structure to the generic pimAL *Fields). Now you can understand why get*Records() does not have to read out the complete data from the device but only the ids: As soon as get*Fields() of a single *Record is called, the data will be read out anyway (it must only be found - that’s where you need to device id for).
Set*Fields( ) sets the fields of the *Record to the given Vector (of *Fields) - that means, set*Fields erases all the "old" fields (the "old" Vector) and replaces them by the "new" fields which are passed in a Vector. This is the place where you have to do your "downward" mapping (mapping the generic pimAL fields to the native data structure).
The usage of these methods to manipulate PIM data works as follows (as described above): Get the *Fields-Vector with get*Fields(), manipulate it (remove datasets, ...) and then store it back in the *Record via set*Fields().
3.3.2 How to support storage of more types of data
Storing more different types of data is relatively easy in this framework. There are basically two alternatives of extending the storage capabilities:
In the first alternative, you may want to support another application (e.g. like Palm's "Expenses"). You would have to define a new create method in PIMFactory and in it's derived classes. Since you will probably only want to support one platform (e.g. Palm) at the moment, you can extend other PIMFactories by creating methods, that simply throw NotSupportedByDeviceExceptions. You should then create interfaces for books and records, as well as a device-independent class for fields. Please consider the portability when designing these interfaces and classes, since they will be the base for implementations on other devices. When you're done, you'll have to implement the device specific classes that implement the interfaces you defined.
In the second alternative, you may want to support a data type that is not stored in an already supported application yet. If there was already a key for this data type, you will simply have to implement ways of writing it to and reading it from the desired device into the corresponding "Record" class. If there's no key, you will have to add the key, to the "Field" class and do the same. Existing applications will not be affected by this change, since they simply ignore fields with that key.
4 Conclusion and Perspective
Concluding we can say, that the pimAL framework provides a good way to access PIM data on different platforms. It might be more complicated and more difficult to understand than a platform specific implementation, but this is the price for independence.
Although the pimAL framework covers most of the functionality of the supported platforms, we have to admit, that there are features of specific implementations that simply cannot be supported. Take search functionalities for example. Every platform offers different capabilities to search for records. The approach to take the maximum flexibility and to implement it on every platform is not realizable, because of some platforms lacking CPU power and data thruput. If you're developing an application that accesses PIM data on the device, you have to decide, if you want to use these features, or if you want to develop your software in a way that makes it more independent from changes or other platforms you haven't thought of initially. The pimAL framework provides a good basis for this.
Finally, we want to remind you of this software being part of the SuperWaba project and thus being free and open source software. There are still many improvements and features, that can be added and maybe also some bugs. We encourage you to fix or at least report bugs and to send in any improvements or proposals you have for pimAL.
--
GilbertFridgen? - 13 Oct 2003