Controlling Yourself. A Framework for Configurable Options

From EDM2
Jump to: navigation, search

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 (EA's).

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 behaviors. 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.

Class Relationship Structure

Set Classes

The option set behaviors have been factored into a base class (BOptionSet) and two implementation classes (INIOptionSet and EAOptionSet). The behaviors 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 behaviors 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 <application name>.<item name> 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 <application name> are ignored. The second operation is to perform a storeSet.

Item Classes

The BOptionItem class provides some default implementations for the behaviors. 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 behaviors 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.

Housekeeping test.cpp Initialize several variables, get an anchor block, and open the test profile
create empty set test.cpp Declare object of type INIOptionset with a string and handle for the profile
INIOptionSet.cpp Constructor for string and handle pass string to the BOptionSet for the application name, then hold onto the handle for subesquent file operations
create items test.cpp Declares for a StrOptionItem and two Option items. Option is a contrived class defined in the file option.h. Both of these objects take a value an d a string

used as the name of the option. The name is passed on to the BOptionItem parent class.

Add items to the set test.cpp The addItem method is invoked for each item
BOptionSet.cpp The addItem method allocates an OptionSetElem object to hold the

BOptionItem * and the name of the item. The element is then added to the set or replaces a previous item of the same name. The size of the item name is recorded.

store the set INIOptionSet.cpp The items are stored on the profile test.ini. The set is traversed and each item is individually written to the profile. The exportdata method

is invoked for the BOptionItem involved. In the case of Item3, StrOptionItem: :exportdata is invoked

StrOptionItem.cpp A FlatItem data structure is allocated along with a buffer to hold the string value. The pointer to this is returned. It will be the callers responsibility to free the areas.
change the values of the items test.cpp The item values are change via the operator= method
reset the items from the store test.cpp The loadset method is invoked to update the items from the values held in the INI file. This restores them to their original values.
INIOptionSet.cpp For each item in the option set: the size of the item is obtained

via the PrfQueryProfileItemSize() call; the item is read from the profle; the importData method for that item is invoked to update the item value in memory

StrOptionItem.cpp The importData method receives a FlatItem structur. The current

string dat is freed, and the new value is kept.

delete some items test.cpp Items 1 and 2 are deleted from the set, and the version of the set on the profile is update via resetSet so that only Item3 remains.
Housekeeping test.cpp The profile data set is closed, and a regular sequential file is

opened for the subsequent EA object testing.

Create an EAOptionSet test.cpp The EAOptionSet constructor is invoked, using the INIOptionSet object from above, amd the file handle for the sequential dataset opened above
EAOptionSet.cpp The constructorsends the set name to the BOptionSet constructor

for later use as the applicaion name. The handle of the file is retained.

reset EA's to known values test.cpp The resetSet operation is invoked which eliminates all option items currently stored as EAs, and replaces them with the current set contents, in this case, Item3.
Exercise the EAmethods test.cpp Items are added to the set; the set is stored; values are

changed and reset in a manner similar to the tests for the INIOptionSet.

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 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 the test program has closed the file, but I have no idea why I could not get it to share read access.