Configuration Plug-in Utilities

Joseph Scheuhammer
Adaptive Technology Resource Centre, University of Toronto

Table of Contents

Introduction

The previous documents described the minimum requirements for implementing a plug-in such as drawing up a properties file in the correct way and implementing a plug-in interface. For configuration plug-ins, however, the Web-4-All jar also includes a library of code that encapsulates a variety of common operations.  The package ca.utoronto.atrc.web4all.configuration provides a set of utilities that facilitate plug-in implementation.  In addition, the Control Hub provides functions that retrieve information defined by the plug-in's ".properties" file.

This document describes these supports available to a developer when implementing a configuration plug-in.  The Control Hub's information retrieval functions are discussed first.

back to top

Property Utilities

When the Web-4-All system launches, it locates plug-ins by examining the "3rd_party" folder, looking for ".properties" files.  It is assumed that each file defines the characteristics of a plug-in.  The Control Hub loads each property file, and stores it according to both its "appID" and "appType" properties.  This enables plug-ins to access their core and local properties by querying the Control Hub.  The following section, "Properties via the Conrol Hub", describes the methods of the ControlHub that a plug-in can use to recover its core properties.  The section following that, "Local Properties - class AbstractSetterLauncher", describes how the plug-in can access its local properties.

Properties via the Control Hub

Each core property is listed below along with a corresponding Control Hub method.  The Control Hub method provides access to the core property.  Note that all properties are read only.  Note also that the first property, appID, is not retrieved from the Control Hub, but from the AccLipInfoPackage instance used to "call" the plug-in.

appID
The appID, or application name, is taken from an AccLipInfoPackage and then passed to the plug-in via the AccLipInfoPackage.getSpecificPrefs() method which returns the <application> element from the AccLIP.  One of the attributes of that element is a name attribute, whose value is the appID.

Note:   The class AbstractSetterLauncher has a setAppID() method, that takes an <application> element as input and determines the appID from it.  Thus, a plug-in derived from AbstractSetterLauncher can call AccLipInfoPackage.getSpecificPrefs(), passing the returned value to setAppID() to retrieve the application name from the preferences document, and store it within the plug-in.

appType
public Vector get3rdPartyAppTypes (String inAppID) throws MissingResourceException
Given the appID, the Control Hub returns a list of application types, each as a String.
prefs
public String get3rdPartyPrefsClass (String inAppID) throws MissingResourceException;
Given the appID, the Control Hub returns the fully qualified name of the Java TM object that implements interface ThirdPartyPrefsWizard.
config
public String get3rdPartyConfigClass (String inAppID) throws MissingResourceException;
Given the appID, the Control Hub returns the fully qualified name of the Java TM object that implements interface SetterLauncher.
exec
public String get3rdPartyExecutable (String inAppID) throws MissingResourceException;
Given the appID, the Control Hub returns the path to the executable. For example, if the plug-in launches the "EpiScreen" application, then this property defines where "EpiScreen" is on the local file system.
ini
public String get3rdPartyIni (String inAppID) throws MissingResourceException;
Given the appID, the Control Hub returns the path to the initialization file.
product.name
public String get3rdPartyFullProductName (String inAppID);
Given the appID, the Control Hub returns the product name.

back to top

Local properties - class AbstractSetterLauncher

As noted earlier, the configuation plug-in API is defined by interface ca.utoronto.atrc.web4all.configuration.SetterLauncher.  That interface has a method initLocalProps().  Since the local properties are understood only by the plug-in, implementors have complete control over how they define these properties, and how to use them.  Web-4-All does not constrain the implementation of initLocalProps() in any way.  However, the class ca.utoronto.atrc.web4all.configuration.AbstractSetterLauncher defines a default way of initializing the plug-in's local properties, and a default method for acquiring a property.

It works as follows:  The Control Hub has a method allowing plug-ins access to their local properties.  This method's signature is:

public ResourceBundle get3rdPartyProperties (String inAppID) throws MissingResourceException;

The plug-in provides its appID, and the Control Hub returns all of the properties for that application as a java.util.ResourceBundle.  If no such application exists as defined by the inAppID argument, then a java.util.MissingResourceException is thrown.

AbstractSetterLauncher implements initLocalProps() by calling the Control Hub's get3rdPartyProperties(), and then storing that ResourceBundle locally.  If the plug-in needs to do more than this, then a sub-class can override this behaviour by first using it as is, and then adding the required extra functionality.

AbstractSetterLauncher also provides a utility method for acquiring a given local property.  A plug-in derived from AbstractSetterLauncher can use this to acquire a known local property.  The method's signature is:

protected String getLocalProperty (String inPropName) throws MissingResourceException;

By passing the name of the property, its value is returned, if any such property exists.  If not, then a MissingResourceException is thrown.  Note that AbstractSetterLauncher expects that its initLocalProps() method to have been called prior to using the getLocalProperty() method; that is, the properties must have been stored internally before they are queried.

back to top

Method Utilities

Nomenclature

In order to distinguish between technology settings as the technology itself uses them, and the corresponding preferences in the AccLIP, they are described in two ways in this document.  The terms "preference" and "preference value" refer to the preference in the AccLIP document.  The terms "parameter" and "setting" refer to the way a third party technology represents that preference.  Both preferences and parameters have two aspects: a name and a value.  For example, suppose the AccLIP has an alternative pointing device preference whose name is "doubleClickSpeed" and whose value is  "0.4".  That preference maps to the Kensington Trackball parameter named "DBL_CLICK_SPEED" and to the value "416".

In summary, "preference" refers to some content within the AccLIP, "parameter" or "setting" to a third-party technology setting, "name" to the name of a preference or parameter, and "value" to its value.

class AbstractSetterLauncher

Translation

What does a configuration plug-in do?  Most of the work involves translating the preferences as defined in the AccLIP schema to a form that a specific technology can use.  The complexity of the translation ranges from simple and straightforward to complicated and involved.  An example of the simple case is where the AccLIP encodes volume as a floating point value in the range [0.0, 1.0].  A given technology might represent volume as a signed single byte integer in the range [-127, 128].  The translation from the AccLIP to the technology's setting is a simple linear relationship.  At the other extreme, speech pitch is represented as a relative value, again in the range [0.0, 1.0], in the AccLIP.  To represent a relatively low pitch, say 0.2, a speech synthesizer might be configured to use an adult male voice.  Similarly, a high-pitched voice is achieved by using a woman's or perhaps a child's voice.

Value Maps

AbstractSetterLauncher has a mechanism for performing arbitrarily complex translation via a mapping lookup table.  That is, a plug-in derived from AbstractSetterLauncher can install any number of "value maps", a "value map chooser", and a "name map".

A value map is a lookup table that defines a relationship between the AccLIP preference value and technology's setting.  Using the voice pitch example from the previous paragraph, the plug-in could use a "voice pitch value map" that maps floating point pitch values to various synthesizer voices.

The value map chooser is another lookup table that, given an technology parameter name, chooses the appropriate value map.  This is most useful when the plug-in employs a number of different value maps for each distinct preference.  In this case, the lookup is performed based on the technology parameter name:  Again, using the voice pitch example, the plug-in sees that there is a "pitch" preference in the AccLIP.  It passes the corresponding parameter name to AbstractSetterLauncher's value map chooser, which looks up the correct value map (here, the pitch map), and then determines the parameter value from the map and the AccLIP value.  This results in the correct parameter value.

Finally, there is a third type of map that associates AccLIP preference names with technology parameter names.  This map is termed a name map.

If you wish to use AbstractSetterLauncher's value map machinery, there are some restrictions.  With respect to the value maps and value map chooser, a plug-in can define them in any way it sees fit, however, they must be derived from java.util.ResourceBundle.  The name map, however, must be a ca.utoronto.atrc.web4all.configuration.SettingsBundle, or be derived from that class.

A SettingsBundle is a java.util.ListResourceBundle with one extra feature.  It has a technology-type characteristic, and a method to query it for that type (SettingsBundle.getTechType()).  The AccLIP schema defines the type returned.

Recall that a technology can satisfy more than one type, e.g., it can be both a screen reader and a screen enhancer.  Since a technology might by multi-type, its configuration plug-in would need to define distinct SettingsBundle instances for each type.  This is consistent with the information passed to the plug-in in the form of a list of AccLipInfoPackage instances.  Each of these is defined in terms of a technology type as well.  There should be a one-to-one correspondence between the AccLipInfoPackage technology types, and the SettingsBundle types.

The SettingsBundle object contains a lookup table whose keys are AccLIP preferences names.  The value associated with each key is an instance of a ca.utoronto.atrc.web4all.configuration.ParameterState.  A ParameterState object is an object that maintains an association between a parameter name, whether that parameter has been "written", whether it is required that it be written, and its default value.  For the moment, consider only the parameter name:  the SettingsBundle performs the function of mapping the AccLIP preference name to a specific technology's parameter name.  That parameter name is then used in conjunction with the value map chooser to locate the appropriate value map.

In a nutshell, here is how a plug-in uses AbstractSetterLauncher's value map system.  First, create a ResourceBundle for each of the technology's settings, that maps an AccLIP preference value to a technology's parameter value.  Each constitutes a value map.

Second, create another ResourceBundle whose keys are the technology parameter names.  Each name maps to one of the value maps created in step one.  This is the value map chooser.

Third, create a SettingsBundle whose keys are the names of AccLIP preferences, and whose values are ParameterState objects that each contains a parameter name.  These parameter names are inputs to the value map chooser created at step two.  Create one such SettingsBundle for each type of technology that the plug-in handles.  An algorithm for mapping from an AccLIP preference to a technology setting is as follows:

  1. Retrieve the technology type from the AccLipInfoPackage.
  2. Use the technology type to choose a SettingsBundle.
  3. Given an AccLIP preference name, use the SettingsBundle to retrieve a ParameterState, and, from it, the corresponding parameter name.
  4. Use that parameter name and the value map chooser to choose the correct value map.
  5. Given an AccLIP preference value, use the value map to transfrom it into the correct parameter value.
  6. Set the technology's parameter with that value.

With the proper initialization, AbstractSetterLauncher performs steps one through five "for free".  The initialization is accomplished via the methods:

protected void init(); and
protected void setUpParameters (SettingsBundle[] inParams, ResourceBundle inValueMapChooser);

The former is abstract, and must be implemented in the derived class.  It is advised that, at least, the derived class call the setUpParameters() method from its init() method.  The first argument is an array of SettingsBundle objects, where each member of the array is distinct in terms of a type of technology.  The second argument is the value map chooser, i.e. the lookup table that maps parameter names to value maps.

The sequence outlined above is accomplished by a number of AbstractSetterLauncher's methods.  Steps two and three are handled by the findParameter() methods.  Steps four and five by the mapValue() method.  Steps two through five are effectively accomplished by handlePref(), by essentially calling findParameter() and mapValue() in succession.  All of these methods are are fully documented in the plug-in API documentation.

Linear Transformations

Value maps are useful for complex relationships between AccLIP preference values and an application's parameter values.  Frequently, there is a simpler mathematical equation that will transform an preference value to the correct parameter setting.  AbstractSetterLauncher provides one of these; specifically, a method that performs a linear transform.  Its signature is:

public float linearCalcTechVal (String inAccLipVal, float inSlope, float inIntercept);

The caller passes in the AccLIP value (x), a slope (m), and intercept (b).  The method returns the value (y) transformed according to the linear equation y = mx + b.

back to top

Parameter "Writing"

Once the translation is complete, the technology's setting must be realized.  In our experience, there are two ways to accomplish this.  First, some technologies take their settings from an initialization file; for example, on Windows, a file with the extension ".ini".  This is a text file consisting of parameter names and values.  When the technology starts up, it configures itself according to the contents of this ".ini" file.

Secondly, some technologies use "registry" values as their parameter settings.   In that case, it is useful to translate the AccLIP preferences into an array of in-memory parameter values, which are then transferred to the appropriate place in the registry.

AbstractSetterLauncher provides methods for both writing to an initialization file, and installing settings into a registry.  The former is accomplished via the createOutput() methods, and the writeSetting() methods.  The latter by the methods createArgsArray(), setArgsArray(), setArgsIndexMap(), and addArgToArray().

If your plug-in requires an initialization file, use one of the createOutput() methods to create the initialization file. The difference in the methods is that they provide different ways of defining the path to the intialization file.  In one case, the path is relative to the directory from which Web-4-All was launched.  In a second case, it is relative the the Web-4-All's plug-in folder.  Finally, a full path to the file can be specified.  In all cases, createOutput()creates and records the file, and returns it as a java.io.PrintWriter.  The writeSetting() methods use this PrintWriter to output the parameter name and its value.  These ".ini" related methods are are fully documented in the plug-in API documentation.

An alternative technique to writing parameters to a file is one that uses an in-memory argument array, and is based on the idea that a command line utility will be used to transfer the settings to something similar to a registry.  The command line arguments are represented as a String array.  The array can either be created by using createArgsArray(), or passed in if setArgsArray() is used.


Note:  The use of a command line utility to write settings to a "registry" was employed to avoid specifically committing to the Windows registry.  The AbstractSetterLauncher class could have been implemented such that it transferred relevant parameter settings directly to the registry, but that would not have been platform neutral.  By deferring the writing of the "registry" to a command line utility, the transfer utility can move the settings to wherever it is appropriate.


It is assumed that the argument array is positional, e.g., that the third argument represents, say, the volume setting.  As such, there must also exist a technique that, based on the parameter, places the setting in the correct position of the array.   Plug-ins that use the argument array feature of AbstractSetterLauncher must also provide an index map via the setArgsIndexMap() method.  The argument index map is a ResourceBundle whose keys are parameter names, and whose value is the index in the argument array for that parameter.  The place to initialize the arguments index map is in the plug-in's implementation of init().  When addArgToArray() is called, it will use the parameter name and argument index map to place the parameter value in the proper position of the argument array.

When all preferences have been processed, the completed argument array can be retrieved via the getArgsArray() method.  At that point, it is up to the plug-in to do what is necessary with the argument list.  That is, AbstractSetterLauncher does not define any method to transfer the settings to the "registry"; it only provides a means to collect the settings.  Like the initialization file writing methods, these argument array methods are fully described in the plug-in API documentation.

Finally, the reader might wonder why AbstractSetterLauncher has a set of methods both for writing settings to an initialization file, and another set for creating an positional parameter list.  Why not factor this functionality out between two implementations of interface SetterLauncher?  The answer is that some plug-ins require both techniques for "writing" settings.  That is, some plug-ins will write certain settings to an initialization file, and other settings to a registry.  Placing the writing utilities all in a single class allows a derived class to write exclusively to an initialization file, or exclusively to a registry, or to both.

back to top

Implementing interface SetterLauncher.doSettings()

doSettings() is one method that AbstractSetterLauncher does not implement.  However, AbstractSetterLauncher does implement a number of other methods out of which an implementation of doSettings() can be built.

Conceptutally, doSettings() amounts to examining the AccLIP preferences passed to it, translating them, and writing the translations.  The AccLIP contains a set of generic preferences, and, potentially, a set of technology-specific preferences.  AbstractSetterLauncher defines two loop methods, one for handling the generic settings, and another for the specific settings.

Generic Loop

The signature of the generic loop method is:

protected void loopThruGenerics (String inTechType, Element inGenericContainer);

Note that the first argument defines the type of technology (e.g., onscreen keyboard). For each generic preference in the inGenericContainer,loopThruGenerics() does the following:

  1. Map the name of AccLIP preference to the technology's parameter name.
  2. Retrieve the value of the preference.
  3. Map that value to the parameter's value.
  4. Write out the parameter value.

Note the emphasis on "Write" in step  four.  At this point, loopThruGenerics() dispatches to the method doWriteSetting(), which is another abstract method of AbstractSetterLauncher.  Its signature is:

protected void doWriteSetting (String inParameter, String inValue);

where inParameter is a parameter name, and inValue is its value.  doWriteSetting() can be implemented by calling one of the writeSetting() methods described above, or the addArgToArray() method, or both as required.  This is why doWriteSetting() is abstract -- how a specific plug-in actually outputs the setting cannot be known by AbstractSetterLauncher.  It is up to the implementor of the plug-in to determine how the setting is "written"; and, they can make use of the two utility methods that "write" the setting.

Specific Loop

The corresponding loop for specific settings, loopThru3rdPartyPrefs(), is identical  in control flow to loopThruGenerics.  It is defined as a separate method, however, since it is likely that an implementation of a plug-in will want to keep loopThruGenerics as is, while completely replacing the technique for handling specific settings. 

back to top

Implementing interface SetterLauncher.doLaunch()

As with doSettings(), AbstractSetterLauncher does not define the doLaunch() method of interface SetterLauncher.  However, there are associated methods that sub-classes should use to track the result of a doLaunch().

It is assumed that plug-ins will make use of the java.lang.Runtime() object and its exec() method to actually launch the third party technology.  Runtime.exec() returns an object of type java.lang.ProcessAbstractSetterLauncher defines the utilities setProcess() and a getProcess() in order to make it easy for a sub-class to keep track of that Process, if required.  In addition to tracking the Process launched, they are useful for the SetterLauncher.kill() method.

Implementing interface SetterLauncher.kill()

AbstractSetterLauncher does provide a basic implementation of the kill() method.  This amounts to shutting down the Process created by doLaunch().  This simple shutdown of the process is not suffiicent in most cases.  Sub-classes should override kill() to first reset the third party technology to some default state before killing the process.  As such, an override of kill() should be written to first reset the technology's configuration, and then call AbstractSetterLauncher's version of kill() to actually shut it down.

Summary

This document has described various utility methods of the Control Hub and the AbstractSetterLauncher class that are useful for implementing a configuration plug-in.  It described how to retrieve global properties of the plug-in from the Control Hub, and how to use the basic implementation in AbstractSetterLauncher to acquire its local properites.  It also described utilities that AbstractSetterLauncher provides for, namely:

back to top


Copyright © 2003-2006 Adaptive Technology Resource Centre, University of Toronto.
All rights reserved.
Last modified: Mar 28, 2006. Joseph Scheuhammer.