Joseph Scheuhammer
Adaptive Technology Resource Centre, University of Toronto
doSettings()
doLaunch()
kill()
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.
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.
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.
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.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.
public Vector get3rdPartyAppTypes (String inAppID) throws
MissingResourceException
String
.public String get3rdPartyPrefsClass (String inAppID) throws
MissingResourceException;
interface ThirdPartyPrefsWizard
.public String get3rdPartyConfigClass (String inAppID)
throws MissingResourceException
;interface
SetterLauncher
.public String get3rdPartyExecutable (String inAppID) throws
MissingResourceException
;public String get3rdPartyIni (String inAppID) throws
MissingResourceException
;public String get3rdPartyFullProductName (String inAppID)
;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.
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.
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.
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:
AccLipInfoPackage
.SettingsBundle
.SettingsBundle
to retrieve a ParameterState
, and, from it, the
corresponding parameter name.With the proper initialization, AbstractSetterLauncher
performs steps one through five "for free". The initialization is
accomplished via the methods:
protected void init();
andprotected 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.
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.
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.
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.
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:
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.
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.
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.Process
. AbstractSetterLauncher
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.
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.
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:
interface
SetterLauncher
. Copyright © 2003-2006 Adaptive Technology Resource Centre,
University of Toronto.
All rights reserved.
Last modified: Mar 28, 2006. Joseph Scheuhammer.