Controlling Yourself. A Framework for Configurable Options

Written by John Holt

Introduction
Most programs have a set of persistent options that are under user control. These options must be managed. This article discusses an approach to uniform management of option sets that may reside in a profile (INI file) or on a file's extended attributes (EAs).

The home for user specified general application defaults varies by system. In a UNIX system, environment variables or entries in .mwmrc (the Motif resource file) are used for system-wide defaults. In Windows and OS/2, INI files are used to hold the general application defaults. The OS/2 file system also provides an additional means of keeping file specific default settings.

Problem and Environment Description

Any general framework of classes must have certain attributes. Two attributes that are germane to this article are programmer-extensibility and harmony with the external environment. The facility for programmer extensions is simply providing a capability for the programmer to add new classes to be stored in the profile or extended attributes. The attribute of harmony with the external environment in this case is using profiles and extended attributes in a way that facilitates the use of existing tools.

Several base and implementation classes were developed. The task of managing persistent option sets is common to many programs, and therefore is an excellent candidate for reusable objects. The classes developed for the article represent an attempt to divide the implementation into classes so as to reuse common behaviours. The model for implementation is individual objects grouped into a set. The set is then stored on either a profile or in EAs.

Class Relationship Structure
Overview

The class structure is depicted in the diagram below. The relationship marked IS-A denotes class derivation. The relationship marked HAS-A shows that the class contains an object of the class. The KeyedOptionSet class is the result of a template generation using the KeyedSequentialSet class in the compiler collection classes library.



Set Classes

The option set behaviours have been factored into a base class (BOptionSet) and two implementation classes (INIOptionSet and EAOptionSet). The behaviours specific to manipulation of EAs or profiles is in EAOptionSet and INIOptionSet respectively.

BOptionSet is concerned with the maintenance of the list of items to be stored or retrieved. The following public methods are implemented:
 * addItem:Adds an item in the form of a pointer to a BOptionItem and a name for the option
 * delItem:Removes an item from the set
 * applName:The character string used as the high order name qualifier for EAs or the application name parameter for INI calls.
 * ~BOptionSet:The virtual destructor used to clean up.

The BOptionSet object implements several protected methods used by the derived classes to access and manipulate the private data members. Those behaviours are:


 * firstItem:Position the set cursor to the first item
 * nextItem:Advance the set cursor to the next item in the set
 * item:Returns the pointer to an item in the set that is at the current cursor position.
 * copy::Makes this set a copy of another set.
 * keySpace:Returns the number of bytes needed to store the item key names.
 * flatSpace:Returns the number of bytes needed to store a flat version of the set of items.
 * itemCount:Returns the number of items in the set.
 * BOptionSet:The object constructors. There is a copy constructor and a constructor taking the application name and producing an empty set.

The KeyedOptionSet is generated from the IKeySet template class which is a keyed sequential set class in the IBM collection classes. A cursor is provided by the template. The object is a set of OptionSetElem objects.

The OptionSetElem is a simple object holding only the name of the option and a pointer to a BOptionItem object.

The derived classes EAOptionSet and INIOptionSet provide the implementations for the loadSet, storeSet, and resetSet as well as the usual expected complement of constructors and an equal operator.

The loadSet method updates each item with the value stored for that item's name. For the case of the items stored as EAs, the name is. and is mono-case. For objects stored in profiles, the name is forced to be in uppercase for consistency. The storeSet method is the reverse of the loadSet.

The resetSet method performs two operations. First, the set of names in the profile or EAs is made to correspond with the set of names in the set. Any names with a prefix other than are ignored. The second operation is to perform a storeSet.

Item Classes

The BOptionItem class provides some default implementations for the behaviours. The class interface is primarily intended to be the communication portal between an option set and the real data objects that are the elements of that set. The interface is:


 * itemType:Returns an enum indicating that the object is a character string or is binary data
 * itemSize:Returns the size of the item in bytes
 * exportData:Returns a flattened version of the item consisting of a length and a pointer to a string of bytes.
 * importData:Accepts a flattened version of the item for updating the real data object.

The StrOptionItem is a class definition for a string data object. The definition provides for the normal string behaviors.

Interface to Programmer Supplied Objects

The interface used by the option set to manipulate the items in memory is BOptionItem. Because all option items are derived from BOptionItem, the correct method is invoked.

When an addItem method is invoked on a set, the set obtains a BOptionItem type pointer to that item. When the set needs the size of the item, e.g. to allocate a buffer, the pointer to a BOptionItem is used to invoke the correct method. Similarly, the importData and exportData methods are invoked to move the value to and from the persistent store.

Adding New Object Classes

There are two different strategies that can be employed for extending the range of types of items that can be used in option sets. The programmer may implement interface behaviours directly in the object to be added, or the programmer can implement a wrapper class. If the class is under control of the programmer, direct implementation is usually the best approach. If the object to be used is provided by a third party class library, or is not a first class object (e.g., an INT) then a wrapper class is the right approach. The sample program uses the later approach because it is the most non-obvious of the two approaches. The reader will notice that the example is contrived.

Walkthrough the Sample/Test Program
The sample/test program illustrates how to create a set, add option items to that set, and store the set of option items. To exercise the code, these tasks were performed for both profile and EA based option sets. The file test.cpp [see test_src.zip within options.zip - Editor] should be followed for this discussion. You may also want look at BOptionSet.cpp and StrOptionItem.cpp [see source.zip within options.zip - Editor] for additional detail. There are several things worth keeping in mind. The flow for the above example is designed to test the logic, and not how the objects would be used in a real application. In a real application, the steps for use would be:
 * 1) The data objects for all of the program options are declared, and initial values are given. All of these items must be derived from BOptionItem. The object may be derived from more than one class.
 * 2) The INIOptionSet item is declared using either the handle to an INI file just for this application, or the handle to the user ini file (HINI_USERPROFILE).
 * 3) The loadSet operation is invoked to pick up any defaults established by the user.

If there are file specific options, then the EAOptionSet object is used as well. The EAOptionSet object would be created from the INIOptionSet object, and if there were any non-file related options, those option items would be removed from the EAOptionSet via the delItem operation.

At an appropriate time, the option sets would be written to either the profile or the EAs or both.

Implementation notes
This section discusses some of the less obvious aspects of the set classes. To warrant the appellation of "less obvious," it had to be something I needed some effort to figure out. Which of course means that they may be completely obvious to you.

Profiles

The API for the profile was just a joy to use (compared to EAs), and the documentation makes everything pretty clear.

EAs

The EAs are not at all the same story. I used two books, and the documentation, to puzzle my way through how EAs worked. The books used were The Art of OS/2 2.1 C Programming, by Panov, Salomon, and Panov; the other book was OS/2 2.1 Application Programmers Guide , by Kelly et. al.

The EAOptionSet operations work on all of the EAs at once. That way there are no windows where a particular EA may change, or the ordinal positions of the EAs change.

The layout of the EA buffer is as follows:
 * FEA2:The head for each item in the buffer. The cbName contains the length of the name field (NULL terminated) and cbValue contains the length of the data portion. The offset to the next item is sizeof(FEA2) + cbValue + cbName + this position + enough to get you to the next mod 4 offset. The value of the cbValue element is the size of the flat data item + two SHORTs.
 * ItemType:At FEA2 + cbName + 1 is the EAT value. In our case it is either EAT_BINARY or EAT_ASCII. There are other options available, but the remainder of this discussion would not apply.
 * Size:The is the size in bytes of the flattened data item

Wrap-up
This article has present a set of classes that can be used to manage an application's set of options items. The basic model is that there exists a set of default values for the options of an application; the user is permitted to modify those options in a persistent manner; and that there may be some sub-set of options that are associated with particular files.

Industrial Strength.

There are several obvious features lacking which are required for an industrial strength implementation. In the case of EAs, there are no checks for exclusive control while writing or reading the set of EAs from the file. In the realm of memory management, there are no safeguards against bad pointers.

One extension worthwhile making is that BOptionItems could have reference counts and perhaps references lists of the option sets where they are members.

Useful Test Tools

There are a couple of REXX scripts included in the zip file. In addition, there are several very useful tools at ftp-os2 such as EABrowse. The REXX script to print EAs only works when the test program has closed the file, but I have no idea why I could not get it to share read access.